Commit message: (for 4.9.3) CVE-2018-14469/ISAKMP: Add a missing bounds check

In ikev1_n_print() check bounds before trying to fetch the replay detection
status.

This fixes a buffer over-read discovered by Bhargava Shastry.

Add a test using the capture file supplied by the reporter(s).
CVE: CVE-2018-14469
CWE: CWE-125
Vulnerability description: The IKEv1 parser in tcpdump before 4.9.3 has a buffer over-read in print-isakmp.c:ikev1_n_print().

Project: tcpdump

ikev1_cert_print(netdissect_options *ndo, u_char tpay _U_,
		 const struct isakmp_gen *ext, u_int item_len,
		 const u_char *ep _U_, uint32_t phase _U_,
		 uint32_t doi0 _U_,
		 uint32_t proto0 _U_, int depth _U_)
{
	const struct ikev1_pl_cert *p;
	struct ikev1_pl_cert cert;
	static const char *certstr[] = {
		"none",	"pkcs7", "pgp", "dns",
		"x509sign", "x509ke", "kerberos", "crl",
		"arl", "spki", "x509attr",
	};

	ND_PRINT((ndo,"%s:", NPSTR(ISAKMP_NPTYPE_CERT)));

	p = (const struct ikev1_pl_cert *)ext;
	ND_TCHECK(*p);
	UNALIGNED_MEMCPY(&cert, ext, sizeof(cert));
	ND_PRINT((ndo," len=%d", item_len - 4));
	ND_PRINT((ndo," type=%s", STR_OR_ID((cert.encode), certstr)));
	if (2 < ndo->ndo_vflag && 4 < item_len) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), item_len - 4))
			goto trunc;
	}
	return (const u_char *)ext + item_len;
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_CERT)));
	return NULL;
}

Project: tcpdump

ikev1_cr_print(netdissect_options *ndo, u_char tpay _U_,
	       const struct isakmp_gen *ext, u_int item_len,
	       const u_char *ep _U_, uint32_t phase _U_, uint32_t doi0 _U_,
	       uint32_t proto0 _U_, int depth _U_)
{
	const struct ikev1_pl_cert *p;
	struct ikev1_pl_cert cert;
	static const char *certstr[] = {
		"none",	"pkcs7", "pgp", "dns",
		"x509sign", "x509ke", "kerberos", "crl",
		"arl", "spki", "x509attr",
	};

	ND_PRINT((ndo,"%s:", NPSTR(ISAKMP_NPTYPE_CR)));

	p = (const struct ikev1_pl_cert *)ext;
	ND_TCHECK(*p);
	UNALIGNED_MEMCPY(&cert, ext, sizeof(cert));
	ND_PRINT((ndo," len=%d", item_len - 4));
	ND_PRINT((ndo," type=%s", STR_OR_ID((cert.encode), certstr)));
	if (2 < ndo->ndo_vflag && 4 < item_len) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), item_len - 4))
			goto trunc;
	}
	return (const u_char *)ext + item_len;
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_CR)));
	return NULL;
}

Project: tcpdump

ikev1_hash_print(netdissect_options *ndo, u_char tpay _U_,
		 const struct isakmp_gen *ext, u_int item_len _U_,
		 const u_char *ep _U_, uint32_t phase _U_, uint32_t doi _U_,
		 uint32_t proto _U_, int depth _U_)
{
	struct isakmp_gen e;

	ND_PRINT((ndo,"%s:", NPSTR(ISAKMP_NPTYPE_HASH)));

	ND_TCHECK(*ext);
	UNALIGNED_MEMCPY(&e, ext, sizeof(e));
	ND_PRINT((ndo," len=%d", ntohs(e.len) - 4));
	if (2 < ndo->ndo_vflag && 4 < ntohs(e.len)) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), ntohs(e.len) - 4))
			goto trunc;
	}
	return (const u_char *)ext + ntohs(e.len);
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_HASH)));
	return NULL;
}

Project: tcpdump

ikev1_ke_print(netdissect_options *ndo, u_char tpay _U_,
	       const struct isakmp_gen *ext, u_int item_len _U_,
	       const u_char *ep _U_, uint32_t phase _U_, uint32_t doi _U_,
	       uint32_t proto _U_, int depth _U_)
{
	struct isakmp_gen e;

	ND_PRINT((ndo,"%s:", NPSTR(ISAKMP_NPTYPE_KE)));

	ND_TCHECK(*ext);
	UNALIGNED_MEMCPY(&e, ext, sizeof(e));
	ND_PRINT((ndo," key len=%d", ntohs(e.len) - 4));
	if (2 < ndo->ndo_vflag && 4 < ntohs(e.len)) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), ntohs(e.len) - 4))
			goto trunc;
	}
	return (const u_char *)ext + ntohs(e.len);
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_KE)));
	return NULL;
}

Project: tcpdump

ikev1_sig_print(netdissect_options *ndo, u_char tpay _U_,
		const struct isakmp_gen *ext, u_int item_len _U_,
		const u_char *ep _U_, uint32_t phase _U_, uint32_t doi _U_,
		uint32_t proto _U_, int depth _U_)
{
	struct isakmp_gen e;

	ND_PRINT((ndo,"%s:", NPSTR(ISAKMP_NPTYPE_SIG)));

	ND_TCHECK(*ext);
	UNALIGNED_MEMCPY(&e, ext, sizeof(e));
	ND_PRINT((ndo," len=%d", ntohs(e.len) - 4));
	if (2 < ndo->ndo_vflag && 4 < ntohs(e.len)) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), ntohs(e.len) - 4))
			goto trunc;
	}
	return (const u_char *)ext + ntohs(e.len);
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_SIG)));
	return NULL;
}

Project: tcpdump

ikev1_vid_print(netdissect_options *ndo, u_char tpay _U_,
		const struct isakmp_gen *ext,
		u_int item_len _U_, const u_char *ep _U_,
		uint32_t phase _U_, uint32_t doi _U_,
		uint32_t proto _U_, int depth _U_)
{
	struct isakmp_gen e;

	ND_PRINT((ndo,"%s:", NPSTR(ISAKMP_NPTYPE_VID)));

	ND_TCHECK(*ext);
	UNALIGNED_MEMCPY(&e, ext, sizeof(e));
	ND_PRINT((ndo," len=%d", ntohs(e.len) - 4));
	if (2 < ndo->ndo_vflag && 4 < ntohs(e.len)) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), ntohs(e.len) - 4))
			goto trunc;
	}
	return (const u_char *)ext + ntohs(e.len);
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_VID)));
	return NULL;
}

Project: tcpdump

ikev2_ID_print(netdissect_options *ndo, u_char tpay,
		const struct isakmp_gen *ext,
		u_int item_len _U_, const u_char *ep _U_,
		uint32_t phase _U_, uint32_t doi _U_,
		uint32_t proto _U_, int depth _U_)
{
	const struct ikev2_id *idp;
	struct ikev2_id id;
	int id_len, idtype_len, i;
	unsigned int dumpascii, dumphex;
	const unsigned char *typedata;

	idp = (const struct ikev2_id *)ext;
	ND_TCHECK(*idp);
	UNALIGNED_MEMCPY(&id, ext, sizeof(id));
	ikev2_pay_print(ndo, NPSTR(tpay), id.h.critical);

	id_len = ntohs(id.h.len);

	ND_PRINT((ndo," len=%d", id_len - 4));
	if (2 < ndo->ndo_vflag && 4 < id_len) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), id_len - 4))
			goto trunc;
	}

	idtype_len =id_len - sizeof(struct ikev2_id);
	dumpascii = 0;
	dumphex   = 0;
	typedata  = (const unsigned char *)(ext)+sizeof(struct ikev2_id);

	switch(id.type) {
	case ID_IPV4_ADDR:
		ND_PRINT((ndo, " ipv4:"));
		dumphex=1;
		break;
	case ID_FQDN:
		ND_PRINT((ndo, " fqdn:"));
		dumpascii=1;
		break;
	case ID_RFC822_ADDR:
		ND_PRINT((ndo, " rfc822:"));
		dumpascii=1;
		break;
	case ID_IPV6_ADDR:
		ND_PRINT((ndo, " ipv6:"));
		dumphex=1;
		break;
	case ID_DER_ASN1_DN:
		ND_PRINT((ndo, " dn:"));
		dumphex=1;
		break;
	case ID_DER_ASN1_GN:
		ND_PRINT((ndo, " gn:"));
		dumphex=1;
		break;
	case ID_KEY_ID:
		ND_PRINT((ndo, " keyid:"));
		dumphex=1;
		break;
	}

	if(dumpascii) {
		ND_TCHECK2(*typedata, idtype_len);
		for(i=0; i<idtype_len; i++) {
			if(ND_ISPRINT(typedata[i])) {
				ND_PRINT((ndo, "%c", typedata[i]));
			} else {
				ND_PRINT((ndo, "."));
			}
		}
	}
	if(dumphex) {
		if (!rawprint(ndo, (const uint8_t *)typedata, idtype_len))
			goto trunc;
	}

	return (const u_char *)ext + id_len;
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(tpay)));
	return NULL;
}

Project: tcpdump

ikev2_auth_print(netdissect_options *ndo, u_char tpay,
		const struct isakmp_gen *ext,
		u_int item_len _U_, const u_char *ep,
		uint32_t phase _U_, uint32_t doi _U_,
		uint32_t proto _U_, int depth _U_)
{
	struct ikev2_auth a;
	const char *v2_auth[]={ "invalid", "rsasig",
				"shared-secret", "dsssig" };
	const u_char *authdata = (const u_char*)ext + sizeof(a);
	unsigned int len;

	ND_TCHECK2(*ext, sizeof(a));
	UNALIGNED_MEMCPY(&a, ext, sizeof(a));
	ikev2_pay_print(ndo, NPSTR(tpay), a.h.critical);
	len = ntohs(a.h.len);

	/*
	 * Our caller has ensured that the length is >= 4.
	 */
	ND_PRINT((ndo," len=%u method=%s", len-4,
		  STR_OR_ID(a.auth_method, v2_auth)));
	if (len > 4) {
		if (ndo->ndo_vflag > 1) {
			ND_PRINT((ndo, " authdata=("));
			if (!rawprint(ndo, (const uint8_t *)authdata, len - sizeof(a)))
				goto trunc;
			ND_PRINT((ndo, ") "));
		} else if (ndo->ndo_vflag) {
			if (!ike_show_somedata(ndo, authdata, ep))
				goto trunc;
		}
	}

	return (const u_char *)ext + len;
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(tpay)));
	return NULL;
}

Project: tcpdump

ikev2_gen_print(netdissect_options *ndo, u_char tpay,
		const struct isakmp_gen *ext)
{
	struct isakmp_gen e;

	ND_TCHECK(*ext);
	UNALIGNED_MEMCPY(&e, ext, sizeof(e));
	ikev2_pay_print(ndo, NPSTR(tpay), e.critical);

	ND_PRINT((ndo," len=%d", ntohs(e.len) - 4));
	if (2 < ndo->ndo_vflag && 4 < ntohs(e.len)) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), ntohs(e.len) - 4))
			goto trunc;
	}
	return (const u_char *)ext + ntohs(e.len);
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(tpay)));
	return NULL;
}

Project: tcpdump

ikev2_ke_print(netdissect_options *ndo, u_char tpay,
		const struct isakmp_gen *ext,
		u_int item_len _U_, const u_char *ep _U_,
		uint32_t phase _U_, uint32_t doi _U_,
		uint32_t proto _U_, int depth _U_)
{
	struct ikev2_ke ke;
	const struct ikev2_ke *k;

	k = (const struct ikev2_ke *)ext;
	ND_TCHECK(*k);
	UNALIGNED_MEMCPY(&ke, ext, sizeof(ke));
	ikev2_pay_print(ndo, NPSTR(tpay), ke.h.critical);

	ND_PRINT((ndo," len=%u group=%s", ntohs(ke.h.len) - 8,
		  STR_OR_ID(ntohs(ke.ke_group), dh_p_map)));

	if (2 < ndo->ndo_vflag && 8 < ntohs(ke.h.len)) {
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(k + 1), ntohs(ke.h.len) - 8))
			goto trunc;
	}
	return (const u_char *)ext + ntohs(ke.h.len);
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(tpay)));
	return NULL;
}

Project: tcpdump
File: print-isakmp.c

ikev2_p_print(netdissect_options *ndo, u_char tpay _U_, int pcount _U_,
	      const struct isakmp_gen *ext, u_int oprop_length,
	      const u_char *ep, int depth)
{
	const struct ikev2_p *p;
	struct ikev2_p prop;
	u_int prop_length;
	const u_char *cp;
	int i;
	int tcount;
	u_char np;
	struct isakmp_gen e;
	u_int item_len;

	p = (const struct ikev2_p *)ext;
	ND_TCHECK(*p);
	UNALIGNED_MEMCPY(&prop, ext, sizeof(prop));

	ikev2_pay_print(ndo, NPSTR(ISAKMP_NPTYPE_P), prop.h.critical);

	/*
	 * ikev2_sa_print() guarantees that this is >= 4.
	 */
	prop_length = oprop_length - 4;
	ND_PRINT((ndo," #%u protoid=%s transform=%d len=%u",
		  prop.p_no,  PROTOIDSTR(prop.prot_id),
		  prop.num_t, oprop_length));
	cp = (const u_char *)(p + 1);

	if (prop.spi_size) {
		if (prop_length < prop.spi_size)
			goto toolong;
		ND_PRINT((ndo," spi="));
		if (!rawprint(ndo, (const uint8_t *)cp, prop.spi_size))
			goto trunc;
		cp += prop.spi_size;
		prop_length -= prop.spi_size;
	}

	/*
	 * Print the transforms.
	 */
	tcount = 0;
	for (np = ISAKMP_NPTYPE_T; np != 0; np = e.np) {
		tcount++;
		ext = (const struct isakmp_gen *)cp;
		if (prop_length < sizeof(*ext))
			goto toolong;
		ND_TCHECK(*ext);
		UNALIGNED_MEMCPY(&e, ext, sizeof(e));

		/*
		 * Since we can't have a payload length of less than 4 bytes,
		 * we need to bail out here if the generic header is nonsensical
		 * or truncated, otherwise we could loop forever processing
		 * zero-length items or otherwise misdissect the packet.
		 */
		item_len = ntohs(e.len);
		if (item_len <= 4)
			goto trunc;

		if (prop_length < item_len)
			goto toolong;
		ND_TCHECK2(*cp, item_len);

		depth++;
		ND_PRINT((ndo,"\n"));
		for (i = 0; i < depth; i++)
			ND_PRINT((ndo,"    "));
		ND_PRINT((ndo,"("));
		if (np == ISAKMP_NPTYPE_T) {
			cp = ikev2_t_print(ndo, tcount, ext, item_len, ep);
			if (cp == NULL) {
				/* error, already reported */
				return NULL;
			}
		} else {
			ND_PRINT((ndo, "%s", NPSTR(np)));
			cp += item_len;
		}
		ND_PRINT((ndo,")"));
		depth--;
		prop_length -= item_len;
	}
	return cp;
toolong:
	/*
	 * Skip the rest of the proposal.
	 */
	cp += prop_length;
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_P)));
	return cp;
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(ISAKMP_NPTYPE_P)));
	return NULL;
}

Project: tcpdump

ikev2_vid_print(netdissect_options *ndo, u_char tpay,
		const struct isakmp_gen *ext,
		u_int item_len _U_, const u_char *ep _U_,
		uint32_t phase _U_, uint32_t doi _U_,
		uint32_t proto _U_, int depth _U_)
{
	struct isakmp_gen e;
	const u_char *vid;
	int i, len;

	ND_TCHECK(*ext);
	UNALIGNED_MEMCPY(&e, ext, sizeof(e));
	ikev2_pay_print(ndo, NPSTR(tpay), e.critical);
	ND_PRINT((ndo," len=%d vid=", ntohs(e.len) - 4));

	vid = (const u_char *)(ext+1);
	len = ntohs(e.len) - 4;
	ND_TCHECK2(*vid, len);
	for(i=0; i<len; i++) {
		if(ND_ISPRINT(vid[i])) ND_PRINT((ndo, "%c", vid[i]));
		else ND_PRINT((ndo, "."));
	}
	if (2 < ndo->ndo_vflag && 4 < len) {
		/* Print the entire payload in hex */
		ND_PRINT((ndo," "));
		if (!rawprint(ndo, (const uint8_t *)(ext + 1), ntohs(e.len) - 4))
			goto trunc;
	}
	return (const u_char *)ext + ntohs(e.len);
trunc:
	ND_PRINT((ndo," [|%s]", NPSTR(tpay)));
	return NULL;
}