CVE-2017-13037: Out-of-bounds Read (CWE-125)
value: The IP parser in tcpdump before 4.9.2 has a buffer over-read in print-ip.c:ip_printts().
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-ip.c, tests/TESTLIST, tests/ip_ts_opts_asan.out
Commit date: 2017-09-13 12:25:44 +0100
Commit message: CVE-2017-13037/IP: Add bounds checks when printing time stamp options.

This fixes a buffer over-read discovered by Bhargava Shastry,
SecT/TU Berlin.

Add a test using the capture file supplied by the reporter(s), modified
so the capture file won't be rejected as an invalid capture.

Diff: diff --git a/print-ip.c b/print-ip.c
index 6831b7aa2d..f8e02c1e79 100644
--- a/print-ip.c
+++ b/print-ip.c
@@ -166,31 +166,33 @@ nextproto4_cksum(netdissect_options *ndo,
 	vec[1].ptr = data;
 	vec[1].len = covlen;
 	return (in_cksum(vec, 2));
 }
 
-static void
+static int
 ip_printts(netdissect_options *ndo,
            register const u_char *cp, u_int length)
 {
 	register u_int ptr;
 	register u_int len;
 	int hoplen;
 	const char *type;
 
 	if (length < 4) {
 		ND_PRINT((ndo, "[bad length %u]", length));
-		return;
+		return (0);
 	}
 	ND_PRINT((ndo, " TS{"));
 	hoplen = ((cp[3]&0xF) != IPOPT_TS_TSONLY) ? 8 : 4;
 	if ((length - 4) & (hoplen-1))
 		ND_PRINT((ndo, "[bad length %u]", length));
+	ND_TCHECK(cp[2]);
 	ptr = cp[2] - 1;
 	len = 0;
 	if (ptr < 4 || ((ptr - 4) & (hoplen-1)) || ptr > length + 1)
 		ND_PRINT((ndo, "[bad ptr %u]", cp[2]));
+	ND_TCHECK(cp[3]);
 	switch (cp[3]&0xF) {
 	case IPOPT_TS_TSONLY:
 		ND_PRINT((ndo, "TSONLY"));
 		break;
 	case IPOPT_TS_TSANDADDR:
@@ -215,10 +217,11 @@ ip_printts(netdissect_options *ndo,
 
 	type = " ";
 	for (len = 4; len < length; len += hoplen) {
 		if (ptr == len)
 			type = " ^ ";
+		ND_TCHECK2(cp[len], hoplen);
 		ND_PRINT((ndo, "%s%d@%s", type, EXTRACT_32BITS(&cp[len+hoplen-4]),
 		       hoplen!=8 ? "" : ipaddr_string(ndo, &cp[len])));
 		type = " ";
 	}
 
@@ -227,10 +230,14 @@ done:
 
 	if (cp[3]>>4)
 		ND_PRINT((ndo, " [%d hops not recorded]} ", cp[3]>>4));
 	else
 		ND_PRINT((ndo, "}"));
+	return (0);
+
+trunc:
+	return (-1);
 }
 
 /*
  * print IP options.
  */
@@ -276,11 +283,12 @@ ip_optprint(netdissect_options *ndo,
 		switch (option_code) {
 		case IPOPT_EOL:
 			return;
 
 		case IPOPT_TS:
-			ip_printts(ndo, cp, option_len);
+			if (ip_printts(ndo, cp, option_len) == -1)
+				goto trunc;
 			break;
 
 		case IPOPT_RR:       /* fall through */
 		case IPOPT_SSRR:
 		case IPOPT_LSRR:
diff --git a/tests/TESTLIST b/tests/TESTLIST
index 27c38b9c31..5732f8ff50 100644
--- a/tests/TESTLIST
+++ b/tests/TESTLIST
@@ -550,10 +550,11 @@ pim_header_asan-2	pim_header_asan-2.pcap		pim_header_asan-2.out	-v
 pim_header_asan-3	pim_header_asan-3.pcap		pim_header_asan-3.out	-v
 pim_header_asan-4	pim_header_asan-4.pcap		pim_header_asan-4.out	-v
 ip6_frag_asan		ip6_frag_asan.pcap		ip6_frag_asan.out	-v
 radius_attr_asan	radius_attr_asan.pcap		radius_attr_asan.out	-v
 ospf6_decode_v3_asan	ospf6_decode_v3_asan.pcap	ospf6_decode_v3_asan.out -v
+ip_ts_opts_asan		ip_ts_opts_asan.pcap		ip_ts_opts_asan.out	-v
 
 # 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
diff --git a/tests/ip_ts_opts_asan.out b/tests/ip_ts_opts_asan.out
new file mode 100644
index 0000000000..ce890b1ba1
--- /dev/null
+++ b/tests/ip_ts_opts_asan.out
@@ -0,0 +1,2 @@
+IP (tos 0xe2,ECT(0), id 32, offset 0, flags [+, DF, rsvd], proto ICMP (1), length 65319, options (timestamp TS{[bad length 14]TS+ADDR ^ 1229070338@0.0.52.112[|ip]), bad cksum a09b (->90a7)!)
+    149.8.33.81 > 95.18.83.227: [|icmp]
diff --git a/tests/ip_ts_opts_asan.pcap b/tests/ip_ts_opts_asan.pcap
new file mode 100644
index 0000000000..c9bb6b46bf
Binary files /dev/null and b/tests/ip_ts_opts_asan.pcap differ

Vulnerable code: static void
		return;
			ip_printts(ndo, cp, option_len);
Fixed code: static int
		return (0);
	ND_TCHECK(cp[2]);
	ND_TCHECK(cp[3]);
		ND_TCHECK2(cp[len], hoplen);
	return (0);

trunc:
	return (-1);
			if (ip_printts(ndo, cp, option_len) == -1)
				goto trunc;
ip_ts_opts_asan		ip_ts_opts_asan.pcap		ip_ts_opts_asan.out	-v
IP (tos 0xe2,ECT(0), id 32, offset 0, flags [+, DF, rsvd], proto ICMP (1), length 65319, options (timestamp TS{[bad length 14]TS+ADDR ^ 1229070338@0.0.52.112[|ip]), bad cksum a09b (->90a7)!)
    149.8.33.81 > 95.18.83.227: [|icmp]