Commit message: (for 4.9.3) CVE-2018-14466/Rx: fix an over-read bug

In rx_cache_insert() and rx_cache_find() properly read the serviceId
field of the rx_header structure as a 16-bit integer. When those
functions tried to read 32 bits the extra 16 bits could be outside of
the bounds checked in rx_print() for the rx_header structure, as
serviceId is the last field in that structure.

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).
CVE: CVE-2018-14466
CWE: CWE-125
Vulnerability description: The Rx parser in tcpdump before 4.9.3 has a buffer over-read in print-rx.c:rx_cache_find() and rx_cache_insert().

Project: tcpdump
File: print-rx.c

cb_print(netdissect_options *ndo,
         register const u_char *bp, int length)
{
	int cb_op;
	unsigned long i;

	if (length <= (int)sizeof(struct rx_header))
		return;

	if (ndo->ndo_snapend - bp + 1 <= (int)(sizeof(struct rx_header) + sizeof(int32_t))) {
		goto trunc;
	}

	/*
	 * Print out the afs call we're invoking.  The table used here was
	 * gleaned from fsint/afscbint.xg
	 */

	cb_op = EXTRACT_32BITS(bp + sizeof(struct rx_header));

	ND_PRINT((ndo, " cb call %s", tok2str(cb_req, "op#%d", cb_op)));

	bp += sizeof(struct rx_header) + 4;

	/*
	 * Print out the afs call we're invoking.  The table used here was
	 * gleaned from fsint/afscbint.xg
	 */

	switch (cb_op) {
		case 204:		/* Callback */
		{
			unsigned long j, t;
			ND_TCHECK2(bp[0], 4);
			j = EXTRACT_32BITS(bp);
			bp += sizeof(int32_t);

			for (i = 0; i < j; i++) {
				FIDOUT();
				if (i != j - 1)
					ND_PRINT((ndo, ","));
			}

			if (j == 0)
				ND_PRINT((ndo, " <none!>"));

			ND_TCHECK_32BITS(bp);
			j = EXTRACT_32BITS(bp);
			bp += sizeof(int32_t);

			if (j != 0)
				ND_PRINT((ndo, ";"));

			for (i = 0; i < j; i++) {
				ND_PRINT((ndo, " ver"));
				INTOUT();
				ND_PRINT((ndo, " expires"));
				DATEOUT();
				ND_TCHECK2(bp[0], 4);
				t = EXTRACT_32BITS(bp);
				bp += sizeof(int32_t);
				tok2str(cb_types, "type %d", t);
			}
			break;
		}
		case 214: {
			ND_PRINT((ndo, " afsuuid"));
			AFSUUIDOUT();
			break;
		}
		default:
			;
	}

	return;

trunc:
	ND_PRINT((ndo, " [|cb]"));
}

Project: tcpdump
File: print-rx.c

fs_print(netdissect_options *ndo,
         register const u_char *bp, int length)
{
	int fs_op;
	unsigned long i;

	if (length <= (int)sizeof(struct rx_header))
		return;

	if (ndo->ndo_snapend - bp + 1 <= (int)(sizeof(struct rx_header) + sizeof(int32_t))) {
		goto trunc;
	}

	/*
	 * Print out the afs call we're invoking.  The table used here was
	 * gleaned from fsint/afsint.xg
	 */

	fs_op = EXTRACT_32BITS(bp + sizeof(struct rx_header));

	ND_PRINT((ndo, " fs call %s", tok2str(fs_req, "op#%d", fs_op)));

	/*
	 * Print out arguments to some of the AFS calls.  This stuff is
	 * all from afsint.xg
	 */

	bp += sizeof(struct rx_header) + 4;

	/*
	 * Sigh.  This is gross.  Ritchie forgive me.
	 */

	switch (fs_op) {
		case 130:	/* Fetch data */
			FIDOUT();
			ND_PRINT((ndo, " offset"));
			UINTOUT();
			ND_PRINT((ndo, " length"));
			UINTOUT();
			break;
		case 131:	/* Fetch ACL */
		case 132:	/* Fetch Status */
		case 143:	/* Old set lock */
		case 144:	/* Old extend lock */
		case 145:	/* Old release lock */
		case 156:	/* Set lock */
		case 157:	/* Extend lock */
		case 158:	/* Release lock */
			FIDOUT();
			break;
		case 135:	/* Store status */
			FIDOUT();
			STOREATTROUT();
			break;
		case 133:	/* Store data */
			FIDOUT();
			STOREATTROUT();
			ND_PRINT((ndo, " offset"));
			UINTOUT();
			ND_PRINT((ndo, " length"));
			UINTOUT();
			ND_PRINT((ndo, " flen"));
			UINTOUT();
			break;
		case 134:	/* Store ACL */
		{
			char a[AFSOPAQUEMAX+1];
			FIDOUT();
			ND_TCHECK2(bp[0], 4);
			i = EXTRACT_32BITS(bp);
			bp += sizeof(int32_t);
			ND_TCHECK2(bp[0], i);
			i = min(AFSOPAQUEMAX, i);
			strncpy(a, (const char *) bp, i);
			a[i] = '\0';
			acl_print(ndo, (u_char *) a, sizeof(a), (u_char *) a + i);
			break;
		}
		case 137:	/* Create file */
		case 141:	/* MakeDir */
			FIDOUT();
			STROUT(AFSNAMEMAX);
			STOREATTROUT();
			break;
		case 136:	/* Remove file */
		case 142:	/* Remove directory */
			FIDOUT();
			STROUT(AFSNAMEMAX);
			break;
		case 138:	/* Rename file */
			ND_PRINT((ndo, " old"));
			FIDOUT();
			STROUT(AFSNAMEMAX);
			ND_PRINT((ndo, " new"));
			FIDOUT();
			STROUT(AFSNAMEMAX);
			break;
		case 139:	/* Symlink */
			FIDOUT();
			STROUT(AFSNAMEMAX);
			ND_PRINT((ndo, " link to"));
			STROUT(AFSNAMEMAX);
			break;
		case 140:	/* Link */
			FIDOUT();
			STROUT(AFSNAMEMAX);
			ND_PRINT((ndo, " link to"));
			FIDOUT();
			break;
		case 148:	/* Get volume info */
			STROUT(AFSNAMEMAX);
			break;
		case 149:	/* Get volume stats */
		case 150:	/* Set volume stats */
			ND_PRINT((ndo, " volid"));
			UINTOUT();
			break;
		case 154:	/* New get volume info */
			ND_PRINT((ndo, " volname"));
			STROUT(AFSNAMEMAX);
			break;
		case 155:	/* Bulk stat */
		case 65536:     /* Inline bulk stat */
		{
			unsigned long j;
			ND_TCHECK2(bp[0], 4);
			j = EXTRACT_32BITS(bp);
			bp += sizeof(int32_t);

			for (i = 0; i < j; i++) {
				FIDOUT();
				if (i != j - 1)
					ND_PRINT((ndo, ","));
			}
			if (j == 0)
				ND_PRINT((ndo, " <none!>"));
			break;
		}
		case 65537:	/* Fetch data 64 */
			FIDOUT();
			ND_PRINT((ndo, " offset"));
			UINT64OUT();
			ND_PRINT((ndo, " length"));
			UINT64OUT();
			break;
		case 65538:	/* Store data 64 */
			FIDOUT();
			STOREATTROUT();
			ND_PRINT((ndo, " offset"));
			UINT64OUT();
			ND_PRINT((ndo, " length"));
			UINT64OUT();
			ND_PRINT((ndo, " flen"));
			UINT64OUT();
			break;
		case 65541:    /* CallBack rx conn address */
			ND_PRINT((ndo, " addr"));
			UINTOUT();
		default:
			;
	}

	return;

trunc:
	ND_PRINT((ndo, " [|fs]"));
}

Project: tcpdump
File: print-rx.c

vldb_reply_print(netdissect_options *ndo,
                 register const u_char *bp, int length, int32_t opcode)
{
	const struct rx_header *rxh;
	unsigned long i;

	if (length < (int)sizeof(struct rx_header))
		return;

	rxh = (const struct rx_header *) bp;

	/*
	 * Print out the afs call we're invoking.  The table used here was
	 * gleaned from vlserver/vldbint.xg.  Check to see if it's a
	 * Ubik call, however.
	 */

	ND_PRINT((ndo, " vldb"));

	if (is_ubik(opcode)) {
		ubik_reply_print(ndo, bp, length, opcode);
		return;
	}

	ND_PRINT((ndo, " reply %s", tok2str(vldb_req, "op#%d", opcode)));

	bp += sizeof(struct rx_header);

	/*
	 * If it was a data packet, interpret the response
	 */

	if (rxh->type == RX_PACKET_TYPE_DATA)
		switch (opcode) {
		case 510:	/* List entry */
			ND_PRINT((ndo, " count"));
			INTOUT();
			ND_PRINT((ndo, " nextindex"));
			INTOUT();
			/*FALLTHROUGH*/
		case 503:	/* Get entry by id */
		case 504:	/* Get entry by name */
		{	unsigned long nservers, j;
			VECOUT(VLNAMEMAX);
			ND_TCHECK2(bp[0], sizeof(int32_t));
			bp += sizeof(int32_t);
			ND_PRINT((ndo, " numservers"));
			ND_TCHECK2(bp[0], sizeof(int32_t));
			nservers = EXTRACT_32BITS(bp);
			bp += sizeof(int32_t);
			ND_PRINT((ndo, " %lu", nservers));
			ND_PRINT((ndo, " servers"));
			for (i = 0; i < 8; i++) {
				ND_TCHECK2(bp[0], sizeof(int32_t));
				if (i < nservers)
					ND_PRINT((ndo, " %s",
					   intoa(((const struct in_addr *) bp)->s_addr)));
				bp += sizeof(int32_t);
			}
			ND_PRINT((ndo, " partitions"));
			for (i = 0; i < 8; i++) {
				ND_TCHECK2(bp[0], sizeof(int32_t));
				j = EXTRACT_32BITS(bp);
				if (i < nservers && j <= 26)
					ND_PRINT((ndo, " %c", 'a' + (int)j));
				else if (i < nservers)
					ND_PRINT((ndo, " %lu", j));
				bp += sizeof(int32_t);
			}
			ND_TCHECK2(bp[0], 8 * sizeof(int32_t));
			bp += 8 * sizeof(int32_t);
			ND_PRINT((ndo, " rwvol"));
			UINTOUT();
			ND_PRINT((ndo, " rovol"));
			UINTOUT();
			ND_PRINT((ndo, " backup"));
			UINTOUT();
		}
			break;
		case 505:	/* Get new volume ID */
			ND_PRINT((ndo, " newvol"));
			UINTOUT();
			break;
		case 521:	/* List entry */
		case 529:	/* List entry U */
			ND_PRINT((ndo, " count"));
			INTOUT();
			ND_PRINT((ndo, " nextindex"));
			INTOUT();
			/*FALLTHROUGH*/
		case 518:	/* Get entry by ID N */
		case 519:	/* Get entry by name N */
		{	unsigned long nservers, j;
			VECOUT(VLNAMEMAX);
			ND_PRINT((ndo, " numservers"));
			ND_TCHECK2(bp[0], sizeof(int32_t));
			nservers = EXTRACT_32BITS(bp);
			bp += sizeof(int32_t);
			ND_PRINT((ndo, " %lu", nservers));
			ND_PRINT((ndo, " servers"));
			for (i = 0; i < 13; i++) {
				ND_TCHECK2(bp[0], sizeof(int32_t));
				if (i < nservers)
					ND_PRINT((ndo, " %s",
					   intoa(((const struct in_addr *) bp)->s_addr)));
				bp += sizeof(int32_t);
			}
			ND_PRINT((ndo, " partitions"));
			for (i = 0; i < 13; i++) {
				ND_TCHECK2(bp[0], sizeof(int32_t));
				j = EXTRACT_32BITS(bp);
				if (i < nservers && j <= 26)
					ND_PRINT((ndo, " %c", 'a' + (int)j));
				else if (i < nservers)
					ND_PRINT((ndo, " %lu", j));
				bp += sizeof(int32_t);
			}
			ND_TCHECK2(bp[0], 13 * sizeof(int32_t));
			bp += 13 * sizeof(int32_t);
			ND_PRINT((ndo, " rwvol"));
			UINTOUT();
			ND_PRINT((ndo, " rovol"));
			UINTOUT();
			ND_PRINT((ndo, " backup"));
			UINTOUT();
		}
			break;
		case 526:	/* Get entry by ID U */
		case 527:	/* Get entry by name U */
		{	unsigned long nservers, j;
			VECOUT(VLNAMEMAX);
			ND_PRINT((ndo, " numservers"));
			ND_TCHECK2(bp[0], sizeof(int32_t));
			nservers = EXTRACT_32BITS(bp);
			bp += sizeof(int32_t);
			ND_PRINT((ndo, " %lu", nservers));
			ND_PRINT((ndo, " servers"));
			for (i = 0; i < 13; i++) {
				if (i < nservers) {
					ND_PRINT((ndo, " afsuuid"));
					AFSUUIDOUT();
				} else {
					ND_TCHECK2(bp[0], 44);
					bp += 44;
				}
			}
			ND_TCHECK2(bp[0], 4 * 13);
			bp += 4 * 13;
			ND_PRINT((ndo, " partitions"));
			for (i = 0; i < 13; i++) {
				ND_TCHECK2(bp[0], sizeof(int32_t));
				j = EXTRACT_32BITS(bp);
				if (i < nservers && j <= 26)
					ND_PRINT((ndo, " %c", 'a' + (int)j));
				else if (i < nservers)
					ND_PRINT((ndo, " %lu", j));
				bp += sizeof(int32_t);
			}
			ND_TCHECK2(bp[0], 13 * sizeof(int32_t));
			bp += 13 * sizeof(int32_t);
			ND_PRINT((ndo, " rwvol"));
			UINTOUT();
			ND_PRINT((ndo, " rovol"));
			UINTOUT();
			ND_PRINT((ndo, " backup"));
			UINTOUT();
		}
		default:
			;
		}

	else {
		/*
		 * Otherwise, just print out the return code
		 */
		ND_PRINT((ndo, " errcode"));
		INTOUT();
	}

	return;

trunc:
	ND_PRINT((ndo, " [|vldb]"));
}