CVE-2017-13009: Out-of-bounds Read (CWE-125)
value: The IPv6 mobility parser in tcpdump before 4.9.2 has a buffer over-read in print-mobility.c:mobility_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-mobility.c, tests/TESTLIST, tests/ipv6-mobility-header-oobr.out
Commit date: 2017-09-13 12:25:44 +0100
Commit message: CVE-2017-13009/IPv6 mobility: Add a bounds check.

This fixes a buffer over-read discovered by Brian 'geeknik' Carpenter.

Add a test using the capture file supplied by the reporter(s).

While we're at it:

Add a comment giving the RFC for IPv6 mobility headers.

Clean up some bounds checks to make it clearer what they're checking, by
matching the subsequent EXTRACT_ calls or memcpy.

For the binding update, if none of the flag bits are set, don't check
the individual flag bits.

Diff: diff --git a/print-mobility.c b/print-mobility.c
index ca5541330..d3ca0cabd 100644
--- a/print-mobility.c
+++ b/print-mobility.c
@@ -26,10 +26,11 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
 
 /* \summary: IPv6 mobility printer */
+/* RFC 3775 */
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
@@ -239,11 +240,11 @@ mobility_print(netdissect_options *ndo,
 		break;
 	case IP6M_HOME_TEST_INIT:
 	case IP6M_CAREOF_TEST_INIT:
 		hlen = IP6M_MINLEN;
 		if (ndo->ndo_vflag) {
-			ND_TCHECK2(*mh, hlen + 8);
+			ND_TCHECK_32BITS(&bp[hlen + 4]);
 			ND_PRINT((ndo, " %s Init Cookie=%08x:%08x",
 			       type == IP6M_HOME_TEST_INIT ? "Home" : "Care-of",
 			       EXTRACT_32BITS(&bp[hlen]),
 			       EXTRACT_32BITS(&bp[hlen + 4])));
 		}
@@ -253,19 +254,19 @@ mobility_print(netdissect_options *ndo,
 	case IP6M_CAREOF_TEST:
 		ND_TCHECK(mh->ip6m_data16[0]);
 		ND_PRINT((ndo, " nonce id=0x%x", EXTRACT_16BITS(&mh->ip6m_data16[0])));
 		hlen = IP6M_MINLEN;
 		if (ndo->ndo_vflag) {
-			ND_TCHECK2(*mh, hlen + 8);
+			ND_TCHECK_32BITS(&bp[hlen + 4]);
 			ND_PRINT((ndo, " %s Init Cookie=%08x:%08x",
 			       type == IP6M_HOME_TEST ? "Home" : "Care-of",
 			       EXTRACT_32BITS(&bp[hlen]),
 			       EXTRACT_32BITS(&bp[hlen + 4])));
 		}
 		hlen += 8;
 		if (ndo->ndo_vflag) {
-			ND_TCHECK2(*mh, hlen + 8);
+			ND_TCHECK_32BITS(&bp[hlen + 4]);
 			ND_PRINT((ndo, " %s Keygen Token=%08x:%08x",
 			       type == IP6M_HOME_TEST ? "Home" : "Care-of",
 			       EXTRACT_32BITS(&bp[hlen]),
 			       EXTRACT_32BITS(&bp[hlen + 4])));
 		}
@@ -273,51 +274,53 @@ mobility_print(netdissect_options *ndo,
 		break;
 	case IP6M_BINDING_UPDATE:
 		ND_TCHECK(mh->ip6m_data16[0]);
 		ND_PRINT((ndo, " seq#=%u", EXTRACT_16BITS(&mh->ip6m_data16[0])));
 		hlen = IP6M_MINLEN;
-		ND_TCHECK2(*mh, hlen + 1);
-		if (bp[hlen] & 0xf0)
+		ND_TCHECK_16BITS(&bp[hlen]);
+		if (bp[hlen] & 0xf0) {
 			ND_PRINT((ndo, " "));
-		if (bp[hlen] & 0x80)
-			ND_PRINT((ndo, "A"));
-		if (bp[hlen] & 0x40)
-			ND_PRINT((ndo, "H"));
-		if (bp[hlen] & 0x20)
-			ND_PRINT((ndo, "L"));
-		if (bp[hlen] & 0x10)
-			ND_PRINT((ndo, "K"));
+			if (bp[hlen] & 0x80)
+				ND_PRINT((ndo, "A"));
+			if (bp[hlen] & 0x40)
+				ND_PRINT((ndo, "H"));
+			if (bp[hlen] & 0x20)
+				ND_PRINT((ndo, "L"));
+			if (bp[hlen] & 0x10)
+				ND_PRINT((ndo, "K"));
+		}
 		/* Reserved (4bits) */
 		hlen += 1;
 		/* Reserved (8bits) */
 		hlen += 1;
-		ND_TCHECK2(*mh, hlen + 2);
+		ND_TCHECK_16BITS(&bp[hlen]);
 		/* units of 4 secs */
 		ND_PRINT((ndo, " lifetime=%u", EXTRACT_16BITS(&bp[hlen]) << 2));
 		hlen += 2;
 		break;
 	case IP6M_BINDING_ACK:
 		ND_TCHECK(mh->ip6m_data8[0]);
 		ND_PRINT((ndo, " status=%u", mh->ip6m_data8[0]));
+		ND_TCHECK(mh->ip6m_data8[1]);
 		if (mh->ip6m_data8[1] & 0x80)
 			ND_PRINT((ndo, " K"));
 		/* Reserved (7bits) */
 		hlen = IP6M_MINLEN;
-		ND_TCHECK2(*mh, hlen + 2);
+		ND_TCHECK_16BITS(&bp[hlen]);
 		ND_PRINT((ndo, " seq#=%u", EXTRACT_16BITS(&bp[hlen])));
 		hlen += 2;
-		ND_TCHECK2(*mh, hlen + 2);
+		ND_TCHECK_16BITS(&bp[hlen]);
 		/* units of 4 secs */
 		ND_PRINT((ndo, " lifetime=%u", EXTRACT_16BITS(&bp[hlen]) << 2));
 		hlen += 2;
 		break;
 	case IP6M_BINDING_ERROR:
 		ND_TCHECK(mh->ip6m_data8[0]);
 		ND_PRINT((ndo, " status=%u", mh->ip6m_data8[0]));
 		/* Reserved */
 		hlen = IP6M_MINLEN;
-		ND_TCHECK2(*mh, hlen + 16);
+		ND_TCHECK2(bp[hlen], 16);
 		ND_PRINT((ndo, " homeaddr %s", ip6addr_string(ndo, &bp[hlen])));
 		hlen += 16;
 		break;
 	default:
 		ND_PRINT((ndo, " len=%u", mh->ip6m_len));
diff --git a/tests/TESTLIST b/tests/TESTLIST
index 17ee6fb73..5dad008ef 100644
--- a/tests/TESTLIST
+++ b/tests/TESTLIST
@@ -437,10 +437,11 @@ ipv6-next-header-oobr-2	ipv6-next-header-oobr-2.pcap	ipv6-next-header-oobr-2.out
 ipv6-rthdr-oobr		ipv6-rthdr-oobr.pcap		ipv6-rthdr-oobr.out
 ieee802.11_tim_ie_oobr	ieee802.11_tim_ie_oobr.pcap	ieee802.11_tim_ie_oobr.out
 decnet-shorthdr-oobr	decnet-shorthdr-oobr.pcap	decnet-shorthdr-oobr.out
 isakmp-3948-oobr-2	isakmp-3948-oobr-2.pcap		isakmp-3948-oobr-2.out
 ieee802.11_rates_oobr	ieee802.11_rates_oobr.pcap	ieee802.11_rates_oobr.out
+ipv6-mobility-header-oobr	ipv6-mobility-header-oobr.pcap	ipv6-mobility-header-oobr.out
 
 # bad packets from Kamil Frankowicz
 snmp-heapoverflow-1	snmp-heapoverflow-1.pcap	snmp-heapoverflow-1.out
 snmp-heapoverflow-2	snmp-heapoverflow-2.pcap	snmp-heapoverflow-2.out
 isoclns-heapoverflow-2	isoclns-heapoverflow-2.pcap	isoclns-heapoverflow-2.out	-e -c1
diff --git a/tests/ipv6-mobility-header-oobr.out b/tests/ipv6-mobility-header-oobr.out
new file mode 100644
index 000000000..8188f8a8f
--- /dev/null
+++ b/tests/ipv6-mobility-header-oobr.out
@@ -0,0 +1 @@
+IP6 3030:3030:3030:3030:3030:3030:3030:3030 > 3030:3030:3030:3030:3030:3030:3030:3030: mobility: BA status=48[|MOBILITY]
diff --git a/tests/ipv6-mobility-header-oobr.pcap b/tests/ipv6-mobility-header-oobr.pcap
new file mode 100644
index 000000000..87f6ef19e
Binary files /dev/null and b/tests/ipv6-mobility-header-oobr.pcap differ

Vulnerable code: 			ND_TCHECK2(*mh, hlen + 8);
			ND_TCHECK2(*mh, hlen + 8);
			ND_TCHECK2(*mh, hlen + 8);
		ND_TCHECK2(*mh, hlen + 1);
		if (bp[hlen] & 0xf0)
		if (bp[hlen] & 0x80)
			ND_PRINT((ndo, "A"));
		if (bp[hlen] & 0x40)
			ND_PRINT((ndo, "H"));
		if (bp[hlen] & 0x20)
			ND_PRINT((ndo, "L"));
		if (bp[hlen] & 0x10)
			ND_PRINT((ndo, "K"));
		ND_TCHECK2(*mh, hlen + 2);
		ND_TCHECK2(*mh, hlen + 2);
		ND_TCHECK2(*mh, hlen + 2);
		ND_TCHECK2(*mh, hlen + 16);
Fixed code: /* RFC 3775 */
			ND_TCHECK_32BITS(&bp[hlen + 4]);
			ND_TCHECK_32BITS(&bp[hlen + 4]);
			ND_TCHECK_32BITS(&bp[hlen + 4]);
		ND_TCHECK_16BITS(&bp[hlen]);
		if (bp[hlen] & 0xf0) {
			if (bp[hlen] & 0x80)
				ND_PRINT((ndo, "A"));
			if (bp[hlen] & 0x40)
				ND_PRINT((ndo, "H"));
			if (bp[hlen] & 0x20)
				ND_PRINT((ndo, "L"));
			if (bp[hlen] & 0x10)
				ND_PRINT((ndo, "K"));
		}
		ND_TCHECK_16BITS(&bp[hlen]);
		ND_TCHECK(mh->ip6m_data8[1]);
		ND_TCHECK_16BITS(&bp[hlen]);
		ND_TCHECK_16BITS(&bp[hlen]);
		ND_TCHECK2(bp[hlen], 16);
ipv6-mobility-header-oobr	ipv6-mobility-header-oobr.pcap	ipv6-mobility-header-oobr.out
IP6 3030:3030:3030:3030:3030:3030:3030:3030 > 3030:3030:3030:3030:3030:3030:3030:3030: mobility: BA status=48[|MOBILITY]