commit 7bf069c2517690262aacbddc437731af991b31a7
Author: Guy Harris <guy@alum.mit.edu>
Date:   Mon Jul 20 17:23:41 2015 -0700

    CVE-2016-7983,7984/Don't use strchr() to scan packet data.
    
    It can't be told to stop at the end of the packet data.  Add a
    fn_printztn() that prints null-terminated strings, with a length check,
    and which returns the number of bytes processed, or 0 if we ran out of
    data.  That means it does the scanning we need, but safely.
    
    Use it in the TFTP and BOOTP printers.
    
    Fixes a heap overflow found with American Fuzzy Lop by Hanno Böck.

Index: tcpdump-3.9.8/print-bootp.c
===================================================================
--- tcpdump-3.9.8.orig/print-bootp.c
+++ tcpdump-3.9.8/print-bootp.c
@@ -135,7 +135,7 @@ bootp_print(register const u_char *cp, u
 	TCHECK2(bp->bp_sname[0], 1);		/* check first char only */
 	if (*bp->bp_sname) {
 		printf("\n\t  sname \"");
-		if (fn_print(bp->bp_sname, snapend)) {
+		if (fn_printztn(bp->bp_sname, (u_int)sizeof bp->bp_sname, snapend)) {
 			putchar('"');
 			fputs(tstr + 1, stdout);
 			return;
@@ -145,7 +145,7 @@ bootp_print(register const u_char *cp, u
 	TCHECK2(bp->bp_file[0], 1);		/* check first char only */
 	if (*bp->bp_file) {
 		printf("\n\t  file \"");
-		if (fn_print(bp->bp_file, snapend)) {
+		if (fn_printztn(bp->bp_file, (u_int)sizeof bp->bp_file, snapend)) {
 			putchar('"');
 			fputs(tstr + 1, stdout);
 			return;
Index: tcpdump-3.9.8/print-tftp.c
===================================================================
--- tcpdump-3.9.8.orig/print-tftp.c
+++ tcpdump-3.9.8/print-tftp.c
@@ -77,7 +77,8 @@ tftp_print(register const u_char *bp, u_
 	register const struct tftphdr *tp;
 	register const char *cp;
 	register const u_char *p;
-	register int opcode, i;
+	register int opcode;
+	u_int ui;
 	static char tstr[] = " [|tftp]";
 
 	tp = (const struct tftphdr *)bp;
@@ -86,9 +87,12 @@ tftp_print(register const u_char *bp, u_
 	printf(" %d", length);
 
 	/* Print tftp request type */
+	if (length < 2)
+		goto trunc;
 	TCHECK(tp->th_opcode);
 	opcode = EXTRACT_16BITS(&tp->th_opcode);
 	cp = tok2str(op2str, "tftp-#%d", opcode);
+	length -= 2;
 	printf(" %s", cp);
 	/* Bail if bogus opcode */
 	if (*cp == 't')
@@ -98,54 +102,79 @@ tftp_print(register const u_char *bp, u_
 
 	case RRQ:
 	case WRQ:
+		p = (const u_char *)tp->th_stuff;
+		if (length == 0)
+			goto trunc;
+		printf(" ");
+		/* Print filename */
+		printf("\"");
+		ui = fn_printztn(p, length, snapend);
+		printf("\"");
+		if (ui == 0)
+			goto trunc;
+		p += ui;
+		length -= ui;
+
+		/* Print the mode - RRQ and WRQ only */
+		if (length == 0)
+			goto trunc;	/* no mode */
+		printf(" ");
+		ui = fn_printztn(p, length, snapend);
+		if (ui == 0)
+			goto trunc;
+		p += ui;
+		length -= ui;
+
+		/* Print options, if any */
+		while (length != 0) {
+			TCHECK(*p);
+			if (*p != '\0')
+			ui = fn_printztn(p, length, snapend);
+			if (ui == 0)
+				goto trunc;
+			p += ui;
+			length -= ui;
+ 		}
+		break;
+
 	case OACK:
-		/*
-		 * XXX Not all arpa/tftp.h's specify th_stuff as any
-		 * array; use address of th_block instead
-		 */
-#ifdef notdef
-		p = (u_char *)tp->th_stuff;
-#else
-		p = (u_char *)&tp->th_block;
-#endif
-		putchar(' ');
-		/* Print filename or first option */
-		if (opcode != OACK)
-			putchar('"');
-		i = fn_print(p, snapend);
-		if (opcode != OACK)
-			putchar('"');
-
-		/* Print the mode (RRQ and WRQ only) and any options */
-		while ((p = (const u_char *)strchr((const char *)p, '\0')) != NULL) {
-			if (length <= (u_int)(p - (const u_char *)&tp->th_block))
-				break;
-			p++;
-			if (*p != '\0') {
-				putchar(' ');
-				fn_print(p, snapend);
-			}
+		p = (const u_char *)tp->th_stuff;
+		/* Print options */
+		while (length != 0) {
+			TCHECK(*p);
+			if (*p != '\0')
+				printf(" ");
+			ui = fn_printztn(p, length, snapend);
+			if (ui == 0)
+				goto trunc;
+			p += ui;
+			length -= ui;
 		}
-		
-		if (i)
-			goto trunc;
 		break;
 
 	case ACK:
 	case DATA:
+		if (length < 2)
+			goto trunc;	/* no block number */
 		TCHECK(tp->th_block);
 		printf(" block %d", EXTRACT_16BITS(&tp->th_block));
 		break;
 
 	case ERROR:
 		/* Print error code string */
+		if (length < 2)
+			goto trunc;	/* no error code */
 		TCHECK(tp->th_code);
-		printf(" %s \"", tok2str(err2str, "tftp-err-#%d \"",
+		printf(" %s", tok2str(err2str, "tftp-err-#%d \"",
 				       EXTRACT_16BITS(&tp->th_code)));
+		length -= 2;
 		/* Print error message string */
-		i = fn_print((const u_char *)tp->th_data, snapend);
+		if (length == 0)
+			goto trunc;	/* no error message */
+		printf(" \"");
+		ui = fn_printztn((const u_char *)tp->th_data, length, snapend);
 		putchar('"');
-		if (i)
+		if (ui == 0)
 			goto trunc;
 		break;
 
Index: tcpdump-3.9.8/interface.h
===================================================================
--- tcpdump-3.9.8.orig/interface.h
+++ tcpdump-3.9.8/interface.h
@@ -140,6 +140,7 @@ extern void ts_print(const struct timeva
 extern void relts_print(int);
 
 extern int fn_print(const u_char *, const u_char *);
+extern u_int fn_printztn(const u_char *, u_int, const u_char *);
 extern int fn_printn(const u_char *, u_int, const u_char *);
 extern int fn_printzp(const u_char *, u_int, const u_char *);
 extern int mask2plen(u_int32_t);
Index: tcpdump-3.9.8/util.c
===================================================================
--- tcpdump-3.9.8.orig/util.c
+++ tcpdump-3.9.8/util.c
@@ -44,6 +44,11 @@ static const char rcsid[] _U_ =
 
 #include "interface.h"
 
+#define ISASCII(c)	(!((c) & 0x80))	/* value is an ASCII code point */
+#define ISPRINT(c)	((c) >= 0x20 && (c) <= 0x7E)
+#define ISGRAPH(c)	((c) > 0x20 && (c) <= 0x7E)
+#define TOASCII(c)	((c) & 0x7F)
+
 /*
  * Print out a null-terminated filename (or other ascii string).
  * If ep is NULL, assume no truncation check is needed.
@@ -77,6 +82,55 @@ fn_print(register const u_char *s, regis
 }
 
 /*
+ * Print out a null-terminated filename (or other ascii string) from
+ * a fixed-length buffer.
+ * If ep is NULL, assume no truncation check is needed.
+ * Return the number of bytes of string processed, including the
+ * terminating null, if not truncated.  Return 0 if truncated.
+ */
+u_int
+fn_printztn(register const u_char *s, register u_int n, register const u_char *ep)
+{
+	register u_int bytes;
+	register u_char c;
+
+	bytes = 0;
+	for (;;) {
+		if (n == 0 || (ep != NULL && s >= ep)) {
+			/*
+			 * Truncated.  This includes "no null before we
+			 * got to the end of the fixed-length buffer".
+			 *
+			 * XXX - BOOTP says "null-terminated", which
+			 * means the maximum length of the string, in
+			 * bytes, is 1 less than the size of the buffer,
+			 * as there must always be a terminating null.
+			 */
+			bytes = 0;
+			break;
+		}
+
+		c = *s++;
+		bytes++;
+		n--;
+		if (c == '\0') {
+			/* End of string */
+			break;
+		}
+		if (!ISASCII(c)) {
+			c = TOASCII(c);
+			printf("M-");
+		}
+		if (!ISPRINT(c)) {
+			c ^= 0x40;	/* DEL to ?, others to alpha */
+			printf("^");
+		}
+		printf("%c", c);
+	}
+	return(bytes);
+}
+
+/*
  * Print out a counted filename (or other ascii string).
  * If ep is NULL, assume no truncation check is needed.
  * Return true if truncated.
