CVE-2017-13000: Out-of-bounds Read (CWE-125)
value: The IEEE 802.15.4 parser in tcpdump before 4.9.2 has a buffer over-read in print-802_15_4.c:ieee802_15_4_if_print().
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 9.8
Published: 2017-09-14T06:29Z
Repository: https://github.com/the-tcpdump-group/tcpdump
Language: Other
Files changed: print-802_15_4.c, tests/802_15_4-oobr-1.out, tests/802_15_4-oobr-2.out, tests/TESTLIST
Commit date: 2017-09-13 12:25:44 +0100
Commit message: CVE-2017-13000/IEEE 802.15.4: Add more bounds checks.

While we're at it, add a bunch of macros for the frame control field's
subfields, have the reserved frame types show the frame type value, use
the same code path for processing source and destination addresses
regardless of whether -v was specified (just leave out the addresses in
non-verbose mode), and return the header length in all cases.

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

Add tests using the capture files supplied by the reporter(s).

Diff: diff --git a/print-802_15_4.c b/print-802_15_4.c
index 6fe6d35b2..a43d0333c 100644
--- a/print-802_15_4.c
+++ b/print-802_15_4.c
@@ -36,146 +36,188 @@
 static const char *ftypes[] = {
 	"Beacon",			/* 0 */
 	"Data",				/* 1 */
 	"ACK",				/* 2 */
 	"Command",			/* 3 */
-	"Reserved",			/* 4 */
-	"Reserved",			/* 5 */
-	"Reserved",			/* 6 */
-	"Reserved",			/* 7 */
+	"Reserved (0x4)",		/* 4 */
+	"Reserved (0x5)",		/* 5 */
+	"Reserved (0x6)",		/* 6 */
+	"Reserved (0x7)",		/* 7 */
 };
 
-static int
-extract_header_length(uint16_t fc)
-{
-	int len = 0;
-
-	switch ((fc >> 10) & 0x3) {
-	case 0x00:
-		if (fc & (1 << 6)) /* intra-PAN with none dest addr */
-			return -1;
-		break;
-	case 0x01:
-		return -1;
-	case 0x02:
-		len += 4;
-		break;
-	case 0x03:
-		len += 10;
-		break;
-	}
-
-	switch ((fc >> 14) & 0x3) {
-	case 0x00:
-		break;
-	case 0x01:
-		return -1;
-	case 0x02:
-		len += 4;
-		break;
-	case 0x03:
-		len += 10;
-		break;
-	}
-
-	if (fc & (1 << 6)) {
-		if (len < 2)
-			return -1;
-		len -= 2;
-	}
-
-	return len;
-}
-
+/*
+ * Frame Control subfields.
+ */
+#define FC_FRAME_TYPE(fc)		((fc) & 0x7)
+#define FC_SECURITY_ENABLED		0x0008
+#define FC_FRAME_PENDING		0x0010
+#define FC_ACK_REQUEST			0x0020
+#define FC_PAN_ID_COMPRESSION		0x0040
+#define FC_DEST_ADDRESSING_MODE(fc)	(((fc) >> 10) & 0x3)
+#define FC_FRAME_VERSION(fc)		(((fc) >> 12) & 0x3)
+#define FC_SRC_ADDRESSING_MODE(fc)	(((fc) >> 14) & 0x3)
+
+#define FC_ADDRESSING_MODE_NONE		0x00
+#define FC_ADDRESSING_MODE_RESERVED	0x01
+#define FC_ADDRESSING_MODE_SHORT	0x02
+#define FC_ADDRESSING_MODE_LONG		0x03
 
 u_int
 ieee802_15_4_if_print(netdissect_options *ndo,
                       const struct pcap_pkthdr *h, const u_char *p)
 {
 	u_int caplen = h->caplen;
-	int hdrlen;
+	u_int hdrlen;
 	uint16_t fc;
 	uint8_t seq;
+	uint16_t panid = 0;
 
 	if (caplen < 3) {
-		ND_PRINT((ndo, "[|802.15.4] %x", caplen));
+		ND_PRINT((ndo, "[|802.15.4]"));
 		return caplen;
 	}
+	hdrlen = 3;
 
 	fc = EXTRACT_LE_16BITS(p);
-	hdrlen = extract_header_length(fc);
-
 	seq = EXTRACT_LE_8BITS(p + 2);
 
 	p += 3;
 	caplen -= 3;
 
-	ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[fc & 0x7]));
+	ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[FC_FRAME_TYPE(fc)]));
 	if (ndo->ndo_vflag)
 		ND_PRINT((ndo,"seq %02x ", seq));
-	if (hdrlen == -1) {
-		ND_PRINT((ndo,"invalid! "));
-		return caplen;
-	}
-
-
-	if (!ndo->ndo_vflag) {
-		p+= hdrlen;
-		caplen -= hdrlen;
-	} else {
-		uint16_t panid = 0;
 
-		switch ((fc >> 10) & 0x3) {
-		case 0x00:
+	/*
+	 * Destination address and PAN ID, if present.
+	 */
+	switch (FC_DEST_ADDRESSING_MODE(fc)) {
+	case FC_ADDRESSING_MODE_NONE:
+		if (fc & FC_PAN_ID_COMPRESSION) {
+			/*
+			 * PAN ID compression; this requires that both
+			 * the source and destination addresses be present,
+			 * but the destination address is missing.
+			 */
+			ND_PRINT((ndo, "[|802.15.4]"));
+			return hdrlen;
+		}
+		if (ndo->ndo_vflag)
 			ND_PRINT((ndo,"none "));
-			break;
-		case 0x01:
+		break;
+	case FC_ADDRESSING_MODE_RESERVED:
+		if (ndo->ndo_vflag)
 			ND_PRINT((ndo,"reserved destination addressing mode"));
-			return 0;
-		case 0x02:
-			panid = EXTRACT_LE_16BITS(p);
-			p += 2;
-			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
-			p += 2;
-			break;
-		case 0x03:
-			panid = EXTRACT_LE_16BITS(p);
-			p += 2;
-			ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p)));
-			p += 8;
-			break;
+		return hdrlen;
+	case FC_ADDRESSING_MODE_SHORT:
+		if (caplen < 2) {
+			ND_PRINT((ndo, "[|802.15.4]"));
+			return hdrlen;
+		}
+		panid = EXTRACT_LE_16BITS(p);
+		p += 2;
+		caplen -= 2;
+		hdrlen += 2;
+		if (caplen < 2) {
+			ND_PRINT((ndo, "[|802.15.4]"));
+			return hdrlen;
 		}
+		if (ndo->ndo_vflag)
+			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p + 2)));
+		p += 2;
+		caplen -= 2;
+		hdrlen += 2;
+		break;
+	case FC_ADDRESSING_MODE_LONG:
+		if (caplen < 2) {
+			ND_PRINT((ndo, "[|802.15.4]"));
+			return hdrlen;
+		}
+		panid = EXTRACT_LE_16BITS(p);
+		p += 2;
+		caplen -= 2;
+		hdrlen += 2;
+		if (caplen < 8) {
+			ND_PRINT((ndo, "[|802.15.4]"));
+			return hdrlen;
+		}
+		if (ndo->ndo_vflag)
+			ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p + 2)));
+		p += 8;
+		caplen -= 8;
+		hdrlen += 8;
+		break;
+	}
+	if (ndo->ndo_vflag)
 		ND_PRINT((ndo,"< "));
 
-		switch ((fc >> 14) & 0x3) {
-		case 0x00:
+	/*
+	 * Source address and PAN ID, if present.
+	 */
+	switch (FC_SRC_ADDRESSING_MODE(fc)) {
+	case FC_ADDRESSING_MODE_NONE:
+		if (ndo->ndo_vflag)
 			ND_PRINT((ndo,"none "));
-			break;
-		case 0x01:
+		break;
+	case FC_ADDRESSING_MODE_RESERVED:
+		if (ndo->ndo_vflag)
 			ND_PRINT((ndo,"reserved source addressing mode"));
-			return 0;
-		case 0x02:
-			if (!(fc & (1 << 6))) {
-				panid = EXTRACT_LE_16BITS(p);
-				p += 2;
+		return 0;
+	case FC_ADDRESSING_MODE_SHORT:
+		if (!(fc & FC_PAN_ID_COMPRESSION)) {
+			/*
+			 * The source PAN ID is not compressed out, so
+			 * fetch it.  (Otherwise, we'll use the destination
+			 * PAN ID, fetched above.)
+			 */
+			if (caplen < 2) {
+				ND_PRINT((ndo, "[|802.15.4]"));
+				return hdrlen;
 			}
-			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
+			panid = EXTRACT_LE_16BITS(p);
 			p += 2;
-			break;
-		case 0x03:
-			if (!(fc & (1 << 6))) {
-				panid = EXTRACT_LE_16BITS(p);
-				p += 2;
+			caplen -= 2;
+			hdrlen += 2;
+		}
+		if (caplen < 2) {
+			ND_PRINT((ndo, "[|802.15.4]"));
+			return hdrlen;
+		}
+		if (ndo->ndo_vflag)
+			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
+		p += 2;
+		caplen -= 2;
+		hdrlen += 2;
+		break;
+	case FC_ADDRESSING_MODE_LONG:
+		if (!(fc & FC_PAN_ID_COMPRESSION)) {
+			/*
+			 * The source PAN ID is not compressed out, so
+			 * fetch it.  (Otherwise, we'll use the destination
+			 * PAN ID, fetched above.)
+			 */
+			if (caplen < 2) {
+				ND_PRINT((ndo, "[|802.15.4]"));
+				return hdrlen;
 			}
-                        ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p)));
-			p += 8;
-			break;
+			panid = EXTRACT_LE_16BITS(p);
+			p += 2;
+			caplen -= 2;
+			hdrlen += 2;
 		}
-
-		caplen -= hdrlen;
+		if (caplen < 8) {
+			ND_PRINT((ndo, "[|802.15.4]"));
+			return hdrlen;
+		}
+		if (ndo->ndo_vflag)
+			ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p)));
+		p += 8;
+		caplen -= 8;
+		hdrlen += 8;
+		break;
 	}
 
 	if (!ndo->ndo_suppress_default_print)
 		ND_DEFAULTPRINT(p, caplen);
 
-	return 0;
+	return hdrlen;
 }
diff --git a/tests/802_15_4-oobr-1.out b/tests/802_15_4-oobr-1.out
new file mode 100644
index 000000000..76fb153c2
--- /dev/null
+++ b/tests/802_15_4-oobr-1.out
@@ -0,0 +1 @@
+IEEE 802.15.4 Beacon packet seq cd [|802.15.4]
diff --git a/tests/802_15_4-oobr-1.pcap b/tests/802_15_4-oobr-1.pcap
new file mode 100644
index 000000000..b2a0182f7
Binary files /dev/null and b/tests/802_15_4-oobr-1.pcap differ
diff --git a/tests/802_15_4-oobr-2.out b/tests/802_15_4-oobr-2.out
new file mode 100644
index 000000000..bc3b866c3
--- /dev/null
+++ b/tests/802_15_4-oobr-2.out
@@ -0,0 +1 @@
+IEEE 802.15.4 Data packet seq 01 [|802.15.4]
diff --git a/tests/802_15_4-oobr-2.pcap b/tests/802_15_4-oobr-2.pcap
new file mode 100644
index 000000000..7542fc1ae
Binary files /dev/null and b/tests/802_15_4-oobr-2.pcap differ
diff --git a/tests/TESTLIST b/tests/TESTLIST
index 0c4d45a11..491af396e 100644
--- a/tests/TESTLIST
+++ b/tests/TESTLIST
@@ -484,10 +484,12 @@ lldp-infinite-loop-1	lldp-infinite-loop-1.pcap	lldp-infinite-loop-1.out		-vvv -e
 lldp-infinite-loop-2	lldp-infinite-loop-2.pcap	lldp-infinite-loop-2.out		-vvv -e
 pimv2-oobr-1		pimv2-oobr-1.pcap		pimv2-oobr-1.out		-vvv -e
 pimv2-oobr-2		pimv2-oobr-2.pcap		pimv2-oobr-2.out		-vvv -e
 pimv2-oobr-3		pimv2-oobr-3.pcap		pimv2-oobr-3.out		-vvv -e
 pimv2-oobr-4		pimv2-oobr-4.pcap		pimv2-oobr-4.out		-vvv -e
+802_15_4-oobr-1		802_15_4-oobr-1.pcap		802_15_4-oobr-1.out	-vvv -e
+802_15_4-oobr-2		802_15_4-oobr-2.pcap		802_15_4-oobr-2.out	-vvv -e
 
 # RTP tests
 # fuzzed pcap
 rtp-seg-fault-1  rtp-seg-fault-1.pcap  rtp-seg-fault-1.out  -v -T rtp
 rtp-seg-fault-2  rtp-seg-fault-2.pcap  rtp-seg-fault-2.out  -v -T rtp

Vulnerable code: 	"Reserved",			/* 4 */
	"Reserved",			/* 5 */
	"Reserved",			/* 6 */
	"Reserved",			/* 7 */
static int
extract_header_length(uint16_t fc)
{
	int len = 0;

	switch ((fc >> 10) & 0x3) {
	case 0x00:
		if (fc & (1 << 6)) /* intra-PAN with none dest addr */
			return -1;
		break;
	case 0x01:
		return -1;
	case 0x02:
		len += 4;
		break;
	case 0x03:
		len += 10;
		break;
	}

	switch ((fc >> 14) & 0x3) {
	case 0x00:
		break;
	case 0x01:
		return -1;
	case 0x02:
		len += 4;
		break;
	case 0x03:
		len += 10;
		break;
	}

	if (fc & (1 << 6)) {
		if (len < 2)
			return -1;
		len -= 2;
	}

	return len;
}

	int hdrlen;
		ND_PRINT((ndo, "[|802.15.4] %x", caplen));
	hdrlen = extract_header_length(fc);

	ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[fc & 0x7]));
	if (hdrlen == -1) {
		ND_PRINT((ndo,"invalid! "));
		return caplen;
	}


	if (!ndo->ndo_vflag) {
		p+= hdrlen;
		caplen -= hdrlen;
	} else {
		uint16_t panid = 0;
		switch ((fc >> 10) & 0x3) {
		case 0x00:
			break;
		case 0x01:
			return 0;
		case 0x02:
			panid = EXTRACT_LE_16BITS(p);
			p += 2;
			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
			p += 2;
			break;
		case 0x03:
			panid = EXTRACT_LE_16BITS(p);
			p += 2;
			ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p)));
			p += 8;
			break;
		switch ((fc >> 14) & 0x3) {
		case 0x00:
			break;
		case 0x01:
			return 0;
		case 0x02:
			if (!(fc & (1 << 6))) {
				panid = EXTRACT_LE_16BITS(p);
				p += 2;
			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
			break;
		case 0x03:
			if (!(fc & (1 << 6))) {
				panid = EXTRACT_LE_16BITS(p);
				p += 2;
                        ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p)));
			p += 8;
			break;

		caplen -= hdrlen;
	return 0;
Fixed code: 	"Reserved (0x4)",		/* 4 */
	"Reserved (0x5)",		/* 5 */
	"Reserved (0x6)",		/* 6 */
	"Reserved (0x7)",		/* 7 */
/*
 * Frame Control subfields.
 */
#define FC_FRAME_TYPE(fc)		((fc) & 0x7)
#define FC_SECURITY_ENABLED		0x0008
#define FC_FRAME_PENDING		0x0010
#define FC_ACK_REQUEST			0x0020
#define FC_PAN_ID_COMPRESSION		0x0040
#define FC_DEST_ADDRESSING_MODE(fc)	(((fc) >> 10) & 0x3)
#define FC_FRAME_VERSION(fc)		(((fc) >> 12) & 0x3)
#define FC_SRC_ADDRESSING_MODE(fc)	(((fc) >> 14) & 0x3)

#define FC_ADDRESSING_MODE_NONE		0x00
#define FC_ADDRESSING_MODE_RESERVED	0x01
#define FC_ADDRESSING_MODE_SHORT	0x02
#define FC_ADDRESSING_MODE_LONG		0x03
	u_int hdrlen;
	uint16_t panid = 0;
		ND_PRINT((ndo, "[|802.15.4]"));
	hdrlen = 3;
	ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[FC_FRAME_TYPE(fc)]));
	/*
	 * Destination address and PAN ID, if present.
	 */
	switch (FC_DEST_ADDRESSING_MODE(fc)) {
	case FC_ADDRESSING_MODE_NONE:
		if (fc & FC_PAN_ID_COMPRESSION) {
			/*
			 * PAN ID compression; this requires that both
			 * the source and destination addresses be present,
			 * but the destination address is missing.
			 */
			ND_PRINT((ndo, "[|802.15.4]"));
			return hdrlen;
		}
		if (ndo->ndo_vflag)
		break;
	case FC_ADDRESSING_MODE_RESERVED:
		if (ndo->ndo_vflag)
		return hdrlen;
	case FC_ADDRESSING_MODE_SHORT:
		if (caplen < 2) {
			ND_PRINT((ndo, "[|802.15.4]"));
			return hdrlen;
		}
		panid = EXTRACT_LE_16BITS(p);
		p += 2;
		caplen -= 2;
		hdrlen += 2;
		if (caplen < 2) {
			ND_PRINT((ndo, "[|802.15.4]"));
			return hdrlen;
		if (ndo->ndo_vflag)
			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p + 2)));
		p += 2;
		caplen -= 2;
		hdrlen += 2;
		break;
	case FC_ADDRESSING_MODE_LONG:
		if (caplen < 2) {
			ND_PRINT((ndo, "[|802.15.4]"));
			return hdrlen;
		}
		panid = EXTRACT_LE_16BITS(p);
		p += 2;
		caplen -= 2;
		hdrlen += 2;
		if (caplen < 8) {
			ND_PRINT((ndo, "[|802.15.4]"));
			return hdrlen;
		}
		if (ndo->ndo_vflag)
			ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p + 2)));
		p += 8;
		caplen -= 8;
		hdrlen += 8;
		break;
	}
	if (ndo->ndo_vflag)
	/*
	 * Source address and PAN ID, if present.
	 */
	switch (FC_SRC_ADDRESSING_MODE(fc)) {
	case FC_ADDRESSING_MODE_NONE:
		if (ndo->ndo_vflag)
		break;
	case FC_ADDRESSING_MODE_RESERVED:
		if (ndo->ndo_vflag)
		return 0;
	case FC_ADDRESSING_MODE_SHORT:
		if (!(fc & FC_PAN_ID_COMPRESSION)) {
			/*
			 * The source PAN ID is not compressed out, so
			 * fetch it.  (Otherwise, we'll use the destination
			 * PAN ID, fetched above.)
			 */
			if (caplen < 2) {
				ND_PRINT((ndo, "[|802.15.4]"));
				return hdrlen;
			panid = EXTRACT_LE_16BITS(p);
			caplen -= 2;
			hdrlen += 2;
		}
		if (caplen < 2) {
			ND_PRINT((ndo, "[|802.15.4]"));
			return hdrlen;
		}
		if (ndo->ndo_vflag)
			ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
		p += 2;
		caplen -= 2;
		hdrlen += 2;
		break;
	case FC_ADDRESSING_MODE_LONG:
		if (!(fc & FC_PAN_ID_COMPRESSION)) {
			/*
			 * The source PAN ID is not compressed out, so
			 * fetch it.  (Otherwise, we'll use the destination
			 * PAN ID, fetched above.)
			 */
			if (caplen < 2) {
				ND_PRINT((ndo, "[|802.15.4]"));
				return hdrlen;
			panid = EXTRACT_LE_16BITS(p);
			p += 2;
			caplen -= 2;
			hdrlen += 2;
		if (caplen < 8) {
			ND_PRINT((ndo, "[|802.15.4]"));
			return hdrlen;
		}
		if (ndo->ndo_vflag)
			ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(ndo, p)));
		p += 8;
		caplen -= 8;
		hdrlen += 8;
		break;
	return hdrlen;
IEEE 802.15.4 Beacon packet seq cd [|802.15.4]
IEEE 802.15.4 Data packet seq 01 [|802.15.4]
802_15_4-oobr-1		802_15_4-oobr-1.pcap		802_15_4-oobr-1.out	-vvv -e
802_15_4-oobr-2		802_15_4-oobr-2.pcap		802_15_4-oobr-2.out	-vvv -e