Commit message: CVE-2017-13036/OSPFv3: Add a bounds check before fetching data

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.
CVE: CVE-2017-13036
CWE: CWE-125
Vulnerability description: The OSPFv3 parser in tcpdump before 4.9.2 has a buffer over-read in print-ospf6.c:ospf6_decode_v3().

Project: tcpdump
File: print-ospf6.c

ospf6_decode_at(netdissect_options *ndo,
                const u_char *cp, const u_int len)
{
	uint16_t authdatalen;

	if (len == 0)
		return 0;
	if (len < OSPF6_AT_HDRLEN)
		goto trunc;
	/* Authentication Type */
	ND_TCHECK2(*cp, 2);
	ND_PRINT((ndo, "\n\tAuthentication Type %s", tok2str(ospf6_auth_type_str, "unknown (0x%04x)", EXTRACT_16BITS(cp))));
	cp += 2;
	/* Auth Data Len */
	ND_TCHECK2(*cp, 2);
	authdatalen = EXTRACT_16BITS(cp);
	ND_PRINT((ndo, ", Length %u", authdatalen));
	if (authdatalen < OSPF6_AT_HDRLEN || authdatalen > len)
		goto trunc;
	cp += 2;
	/* Reserved */
	ND_TCHECK2(*cp, 2);
	cp += 2;
	/* Security Association ID */
	ND_TCHECK2(*cp, 2);
	ND_PRINT((ndo, ", SAID %u", EXTRACT_16BITS(cp)));
	cp += 2;
	/* Cryptographic Sequence Number (High-Order 32 Bits) */
	ND_TCHECK2(*cp, 4);
	ND_PRINT((ndo, ", CSN 0x%08x", EXTRACT_32BITS(cp)));
	cp += 4;
	/* Cryptographic Sequence Number (Low-Order 32 Bits) */
	ND_TCHECK2(*cp, 4);
	ND_PRINT((ndo, ":%08x", EXTRACT_32BITS(cp)));
	cp += 4;
	/* Authentication Data */
	ND_TCHECK2(*cp, authdatalen - OSPF6_AT_HDRLEN);
	if (ndo->ndo_vflag > 1)
		print_unknown_data(ndo,cp, "\n\tAuthentication Data ", authdatalen - OSPF6_AT_HDRLEN);
	return 0;

trunc:
	return 1;
}

Project: tcpdump
File: print-ospf6.c

ospf6_decode_v3_trailer(netdissect_options *ndo,
                        const struct ospf6hdr *op, const u_char *cp, const unsigned len)
{
	int llslen = 0;
	int lls_hello = 0;
	int lls_dd = 0;

	if (op->ospf6_type == OSPF_TYPE_HELLO) {
		const struct hello6 *hellop = (const struct hello6 *)((const uint8_t *)op + OSPF6HDR_LEN);
		if (EXTRACT_32BITS(&hellop->hello_options) & OSPF6_OPTION_L)
			lls_hello = 1;
	} else if (op->ospf6_type == OSPF_TYPE_DD) {
		const struct dd6 *ddp = (const struct dd6 *)((const uint8_t *)op + OSPF6HDR_LEN);
		if (EXTRACT_32BITS(&ddp->db_options) & OSPF6_OPTION_L)
			lls_dd = 1;
	}
	if ((lls_hello || lls_dd) && (llslen = ospf6_print_lls(ndo, cp, len)) < 0)
		goto trunc;
	return ospf6_decode_at(ndo, cp + llslen, len - llslen);

trunc:
	return 1;
}

Project: tcpdump
File: print-ospf6.c

ospf6_print(netdissect_options *ndo,
            register const u_char *bp, register u_int length)
{
	register const struct ospf6hdr *op;
	register const u_char *dataend;
	register const char *cp;
	uint16_t datalen;

	op = (const struct ospf6hdr *)bp;

	/* If the type is valid translate it, or just print the type */
	/* value.  If it's not valid, say so and return */
	ND_TCHECK(op->ospf6_type);
	cp = tok2str(ospf6_type_values, "unknown packet type (%u)", op->ospf6_type);
	ND_PRINT((ndo, "OSPFv%u, %s, length %d", op->ospf6_version, cp, length));
	if (*cp == 'u') {
		return;
	}

	if(!ndo->ndo_vflag) { /* non verbose - so lets bail out here */
		return;
	}

	/* OSPFv3 data always comes first and optional trailing data may follow. */
	ND_TCHECK(op->ospf6_len);
	datalen = EXTRACT_16BITS(&op->ospf6_len);
	if (datalen > length) {
		ND_PRINT((ndo, " [len %d]", datalen));
		return;
	}
	dataend = bp + datalen;

	ND_TCHECK(op->ospf6_routerid);
	ND_PRINT((ndo, "\n\tRouter-ID %s", ipaddr_string(ndo, &op->ospf6_routerid)));

	ND_TCHECK(op->ospf6_areaid);
	if (EXTRACT_32BITS(&op->ospf6_areaid) != 0)
		ND_PRINT((ndo, ", Area %s", ipaddr_string(ndo, &op->ospf6_areaid)));
	else
		ND_PRINT((ndo, ", Backbone Area"));
	ND_TCHECK(op->ospf6_instanceid);
	if (op->ospf6_instanceid)
		ND_PRINT((ndo, ", Instance %u", op->ospf6_instanceid));

	/* Do rest according to version.	 */
	switch (op->ospf6_version) {

	case 3:
		/* ospf version 3 */
		if (ospf6_decode_v3(ndo, op, dataend) ||
		    ospf6_decode_v3_trailer(ndo, op, dataend, length - datalen))
			goto trunc;
		break;
	}			/* end switch on version */

	return;
trunc:
	ND_PRINT((ndo, "%s", tstr));
}

Project: tcpdump
File: print-ospf6.c

ospf6_print_lls(netdissect_options *ndo,
                const u_char *cp, const u_int len)
{
	uint16_t llsdatalen;

	if (len == 0)
		return 0;
	if (len < OSPF_LLS_HDRLEN)
		goto trunc;
	/* Checksum */
	ND_TCHECK2(*cp, 2);
	ND_PRINT((ndo, "\n\tLLS Checksum 0x%04x", EXTRACT_16BITS(cp)));
	cp += 2;
	/* LLS Data Length */
	ND_TCHECK2(*cp, 2);
	llsdatalen = EXTRACT_16BITS(cp);
	ND_PRINT((ndo, ", Data Length %u", llsdatalen));
	if (llsdatalen < OSPF_LLS_HDRLEN || llsdatalen > len)
		goto trunc;
	cp += 2;
	/* LLS TLVs */
	ND_TCHECK2(*cp, llsdatalen - OSPF_LLS_HDRLEN);
	/* FIXME: code in print-ospf.c can be reused to decode the TLVs */

	return llsdatalen;
trunc:
	return -1;
}

Project: tcpdump
File: print-ospf6.c

ospf6_print_ls_type(netdissect_options *ndo,
                    register u_int ls_type, register const rtrid_t *ls_stateid)
{
        ND_PRINT((ndo, "\n\t    %s LSA (%d), %s Scope%s, LSA-ID %s",
               tok2str(ospf6_lsa_values, "Unknown", ls_type & LS_TYPE_MASK),
               ls_type & LS_TYPE_MASK,
               tok2str(ospf6_ls_scope_values, "Unknown", ls_type & LS_SCOPE_MASK),
               ls_type &0x8000 ? ", transitive" : "", /* U-bit */
               ipaddr_string(ndo, ls_stateid)));
}

Project: tcpdump
File: print-ospf6.c

ospf6_print_lsa(netdissect_options *ndo,
                register const struct lsa6 *lsap, const u_char *dataend)
{
	register const struct rlalink6 *rlp;
#if 0
	register const struct tos_metric *tosp;
#endif
	register const rtrid_t *ap;
#if 0
	register const struct aslametric *almp;
	register const struct mcla *mcp;
#endif
	register const struct llsa *llsap;
	register const struct lsa6_prefix *lsapp;
#if 0
	register const uint32_t *lp;
#endif
	register u_int prefixes;
	register int bytelen;
	register u_int length, lsa_length;
	uint32_t flags32;
	const uint8_t *tptr;

	if (ospf6_print_lshdr(ndo, &lsap->ls_hdr, dataend))
		return (1);
	ND_TCHECK(lsap->ls_hdr.ls_length);
        length = EXTRACT_16BITS(&lsap->ls_hdr.ls_length);

	/*
	 * The LSA length includes the length of the header;
	 * it must have a value that's at least that length.
	 * If it does, find the length of what follows the
	 * header.
	 */
        if (length < sizeof(struct lsa6_hdr) || (const u_char *)lsap + length > dataend)
        	return (1);
        lsa_length = length - sizeof(struct lsa6_hdr);
        tptr = (const uint8_t *)lsap+sizeof(struct lsa6_hdr);

	switch (EXTRACT_16BITS(&lsap->ls_hdr.ls_type)) {
	case LS_TYPE_ROUTER | LS_SCOPE_AREA:
		if (lsa_length < sizeof (lsap->lsa_un.un_rla.rla_options))
			return (1);
		lsa_length -= sizeof (lsap->lsa_un.un_rla.rla_options);
		ND_TCHECK(lsap->lsa_un.un_rla.rla_options);
		ND_PRINT((ndo, "\n\t      Options [%s]",
		          bittok2str(ospf6_option_values, "none",
		          EXTRACT_32BITS(&lsap->lsa_un.un_rla.rla_options))));
		ND_PRINT((ndo, ", RLA-Flags [%s]",
		          bittok2str(ospf6_rla_flag_values, "none",
		          lsap->lsa_un.un_rla.rla_flags)));

		rlp = lsap->lsa_un.un_rla.rla_link;
		while (lsa_length != 0) {
			if (lsa_length < sizeof (*rlp))
				return (1);
			lsa_length -= sizeof (*rlp);
			ND_TCHECK(*rlp);
			switch (rlp->link_type) {

			case RLA_TYPE_VIRTUAL:
				ND_PRINT((ndo, "\n\t      Virtual Link: Neighbor Router-ID %s"
                                       "\n\t      Neighbor Interface-ID %s, Interface %s",
                                       ipaddr_string(ndo, &rlp->link_nrtid),
                                       ipaddr_string(ndo, &rlp->link_nifid),
                                       ipaddr_string(ndo, &rlp->link_ifid)));
                                break;

			case RLA_TYPE_ROUTER:
				ND_PRINT((ndo, "\n\t      Neighbor Router-ID %s"
                                       "\n\t      Neighbor Interface-ID %s, Interface %s",
                                       ipaddr_string(ndo, &rlp->link_nrtid),
                                       ipaddr_string(ndo, &rlp->link_nifid),
                                       ipaddr_string(ndo, &rlp->link_ifid)));
				break;

			case RLA_TYPE_TRANSIT:
				ND_PRINT((ndo, "\n\t      Neighbor Network-ID %s"
                                       "\n\t      Neighbor Interface-ID %s, Interface %s",
				    ipaddr_string(ndo, &rlp->link_nrtid),
				    ipaddr_string(ndo, &rlp->link_nifid),
				    ipaddr_string(ndo, &rlp->link_ifid)));
				break;

			default:
				ND_PRINT((ndo, "\n\t      Unknown Router Links Type 0x%02x",
				    rlp->link_type));
				return (0);
			}
			ND_PRINT((ndo, ", metric %d", EXTRACT_16BITS(&rlp->link_metric)));
			rlp++;
		}
		break;

	case LS_TYPE_NETWORK | LS_SCOPE_AREA:
		if (lsa_length < sizeof (lsap->lsa_un.un_nla.nla_options))
			return (1);
		lsa_length -= sizeof (lsap->lsa_un.un_nla.nla_options);
		ND_TCHECK(lsap->lsa_un.un_nla.nla_options);
		ND_PRINT((ndo, "\n\t      Options [%s]",
		          bittok2str(ospf6_option_values, "none",
		          EXTRACT_32BITS(&lsap->lsa_un.un_nla.nla_options))));

		ND_PRINT((ndo, "\n\t      Connected Routers:"));
		ap = lsap->lsa_un.un_nla.nla_router;
		while (lsa_length != 0) {
			if (lsa_length < sizeof (*ap))
				return (1);
			lsa_length -= sizeof (*ap);
			ND_TCHECK(*ap);
			ND_PRINT((ndo, "\n\t\t%s", ipaddr_string(ndo, ap)));
			++ap;
		}
		break;

	case LS_TYPE_INTER_AP | LS_SCOPE_AREA:
		if (lsa_length < sizeof (lsap->lsa_un.un_inter_ap.inter_ap_metric))
			return (1);
		lsa_length -= sizeof (lsap->lsa_un.un_inter_ap.inter_ap_metric);
		ND_TCHECK(lsap->lsa_un.un_inter_ap.inter_ap_metric);
		ND_PRINT((ndo, ", metric %u",
			EXTRACT_32BITS(&lsap->lsa_un.un_inter_ap.inter_ap_metric) & SLA_MASK_METRIC));

		tptr = (const uint8_t *)lsap->lsa_un.un_inter_ap.inter_ap_prefix;
		while (lsa_length != 0) {
			bytelen = ospf6_print_lsaprefix(ndo, tptr, lsa_length);
			if (bytelen < 0)
				goto trunc;
			lsa_length -= bytelen;
			tptr += bytelen;
		}
		break;

	case LS_TYPE_ASE | LS_SCOPE_AS:
		if (lsa_length < sizeof (lsap->lsa_un.un_asla.asla_metric))
			return (1);
		lsa_length -= sizeof (lsap->lsa_un.un_asla.asla_metric);
		ND_TCHECK(lsap->lsa_un.un_asla.asla_metric);
		flags32 = EXTRACT_32BITS(&lsap->lsa_un.un_asla.asla_metric);
		ND_PRINT((ndo, "\n\t     Flags [%s]",
		          bittok2str(ospf6_asla_flag_values, "none", flags32)));
		ND_PRINT((ndo, " metric %u",
		       EXTRACT_32BITS(&lsap->lsa_un.un_asla.asla_metric) &
		       ASLA_MASK_METRIC));

		tptr = (const uint8_t *)lsap->lsa_un.un_asla.asla_prefix;
		lsapp = (const struct lsa6_prefix *)tptr;
		bytelen = ospf6_print_lsaprefix(ndo, tptr, lsa_length);
		if (bytelen < 0)
			goto trunc;
		lsa_length -= bytelen;
		tptr += bytelen;

		if ((flags32 & ASLA_FLAG_FWDADDR) != 0) {
			const struct in6_addr *fwdaddr6;

			fwdaddr6 = (const struct in6_addr *)tptr;
			if (lsa_length < sizeof (*fwdaddr6))
				return (1);
			lsa_length -= sizeof (*fwdaddr6);
			ND_TCHECK(*fwdaddr6);
			ND_PRINT((ndo, " forward %s",
			       ip6addr_string(ndo, fwdaddr6)));
			tptr += sizeof(*fwdaddr6);
		}

		if ((flags32 & ASLA_FLAG_ROUTETAG) != 0) {
			if (lsa_length < sizeof (uint32_t))
				return (1);
			lsa_length -= sizeof (uint32_t);
			ND_TCHECK(*(const uint32_t *)tptr);
			ND_PRINT((ndo, " tag %s",
			       ipaddr_string(ndo, (const uint32_t *)tptr)));
			tptr += sizeof(uint32_t);
		}

		if (lsapp->lsa_p_metric) {
			if (lsa_length < sizeof (uint32_t))
				return (1);
			lsa_length -= sizeof (uint32_t);
			ND_TCHECK(*(const uint32_t *)tptr);
			ND_PRINT((ndo, " RefLSID: %s",
			       ipaddr_string(ndo, (const uint32_t *)tptr)));
			tptr += sizeof(uint32_t);
		}
		break;

	case LS_TYPE_LINK:
		/* Link LSA */
		llsap = &lsap->lsa_un.un_llsa;
		if (lsa_length < sizeof (llsap->llsa_priandopt))
			return (1);
		lsa_length -= sizeof (llsap->llsa_priandopt);
		ND_TCHECK(llsap->llsa_priandopt);
		ND_PRINT((ndo, "\n\t      Options [%s]",
		          bittok2str(ospf6_option_values, "none",
		          EXTRACT_32BITS(&llsap->llsa_options))));

		if (lsa_length < sizeof (llsap->llsa_lladdr) + sizeof (llsap->llsa_nprefix))
			return (1);
		lsa_length -= sizeof (llsap->llsa_lladdr) + sizeof (llsap->llsa_nprefix);
                prefixes = EXTRACT_32BITS(&llsap->llsa_nprefix);
		ND_PRINT((ndo, "\n\t      Priority %d, Link-local address %s, Prefixes %d:",
                       llsap->llsa_priority,
                       ip6addr_string(ndo, &llsap->llsa_lladdr),
                       prefixes));

		tptr = (const uint8_t *)llsap->llsa_prefix;
		while (prefixes > 0) {
			bytelen = ospf6_print_lsaprefix(ndo, tptr, lsa_length);
			if (bytelen < 0)
				goto trunc;
			prefixes--;
			lsa_length -= bytelen;
			tptr += bytelen;
		}
		break;

	case LS_TYPE_INTRA_AP | LS_SCOPE_AREA:
		/* Intra-Area-Prefix LSA */
		if (lsa_length < sizeof (lsap->lsa_un.un_intra_ap.intra_ap_rtid))
			return (1);
		lsa_length -= sizeof (lsap->lsa_un.un_intra_ap.intra_ap_rtid);
		ND_TCHECK(lsap->lsa_un.un_intra_ap.intra_ap_rtid);
		ospf6_print_ls_type(ndo,
			EXTRACT_16BITS(&lsap->lsa_un.un_intra_ap.intra_ap_lstype),
			&lsap->lsa_un.un_intra_ap.intra_ap_lsid);

		if (lsa_length < sizeof (lsap->lsa_un.un_intra_ap.intra_ap_nprefix))
			return (1);
		lsa_length -= sizeof (lsap->lsa_un.un_intra_ap.intra_ap_nprefix);
		ND_TCHECK(lsap->lsa_un.un_intra_ap.intra_ap_nprefix);
                prefixes = EXTRACT_16BITS(&lsap->lsa_un.un_intra_ap.intra_ap_nprefix);
		ND_PRINT((ndo, "\n\t      Prefixes %d:", prefixes));

		tptr = (const uint8_t *)lsap->lsa_un.un_intra_ap.intra_ap_prefix;
		while (prefixes > 0) {
			bytelen = ospf6_print_lsaprefix(ndo, tptr, lsa_length);
			if (bytelen < 0)
				goto trunc;
			prefixes--;
			lsa_length -= bytelen;
			tptr += bytelen;
		}
		break;

        case LS_TYPE_GRACE | LS_SCOPE_LINKLOCAL:
                if (ospf_print_grace_lsa(ndo, tptr, lsa_length) == -1) {
                    return 1;
                }
                break;

        case LS_TYPE_INTRA_ATE | LS_SCOPE_LINKLOCAL:
                if (ospf_print_te_lsa(ndo, tptr, lsa_length) == -1) {
                    return 1;
                }
                break;

	default:
                if(!print_unknown_data(ndo,tptr,
                                       "\n\t      ",
                                       lsa_length)) {
                    return (1);
                }
                break;
	}

	return (0);
trunc:
	return (1);
}

Project: tcpdump
File: print-ospf6.c

ospf6_print_lsaprefix(netdissect_options *ndo,
                      const uint8_t *tptr, u_int lsa_length)
{
	const struct lsa6_prefix *lsapp = (const struct lsa6_prefix *)tptr;
	u_int wordlen;
	struct in6_addr prefix;

	if (lsa_length < sizeof (*lsapp) - IPV6_ADDR_LEN_BYTES)
		goto trunc;
	lsa_length -= sizeof (*lsapp) - IPV6_ADDR_LEN_BYTES;
	ND_TCHECK2(*lsapp, sizeof (*lsapp) - IPV6_ADDR_LEN_BYTES);
	wordlen = (lsapp->lsa_p_len + 31) / 32;
	if (wordlen * 4 > sizeof(struct in6_addr)) {
		ND_PRINT((ndo, " bogus prefixlen /%d", lsapp->lsa_p_len));
		goto trunc;
	}
	if (lsa_length < wordlen * 4)
		goto trunc;
	lsa_length -= wordlen * 4;
	ND_TCHECK2(lsapp->lsa_p_prefix, wordlen * 4);
	memset(&prefix, 0, sizeof(prefix));
	memcpy(&prefix, lsapp->lsa_p_prefix, wordlen * 4);
	ND_PRINT((ndo, "\n\t\t%s/%d", ip6addr_string(ndo, &prefix),
		lsapp->lsa_p_len));
        if (lsapp->lsa_p_opt) {
            ND_PRINT((ndo, ", Options [%s]",
                   bittok2str(ospf6_lsa_prefix_option_values,
                              "none", lsapp->lsa_p_opt)));
        }
        ND_PRINT((ndo, ", metric %u", EXTRACT_16BITS(&lsapp->lsa_p_metric)));
	return sizeof(*lsapp) - IPV6_ADDR_LEN_BYTES + wordlen * 4;

trunc:
	return -1;
}