Commit message: (for 4.9.3) CVE-2018-16300/BGP: prevent stack exhaustion

Enforce a limit on how many times bgp_attr_print() can recurse.

This fixes a stack exhaustion discovered by Include Security working
under the Mozilla SOS program in 2018 by means of code audit.
CVE: CVE-2018-16300
CWE: CWE-674
Vulnerability description: The BGP parser in tcpdump before 4.9.3 allows stack consumption in print-bgp.c:bgp_attr_print() because of unlimited recursion.

Project: tcpdump
File: print-bgp.c

bgp_capabilities_print(netdissect_options *ndo,
                       const u_char *opt, int caps_len)
{
	int cap_type, cap_len, tcap_len, cap_offset;
        int i = 0;

        while (i < caps_len) {
                ND_TCHECK2(opt[i], BGP_CAP_HEADER_SIZE);
                cap_type=opt[i];
                cap_len=opt[i+1];
                tcap_len=cap_len;
                ND_PRINT((ndo, "\n\t      %s (%u), length: %u",
                       tok2str(bgp_capcode_values, "Unknown",
                                  cap_type),
                       cap_type,
                       cap_len));
                ND_TCHECK2(opt[i+2], cap_len);
                switch (cap_type) {
                case BGP_CAPCODE_MP:
                    /* AFI (16 bits), Reserved (8 bits), SAFI (8 bits) */
                    ND_TCHECK_8BITS(opt + i + 5);
                    ND_PRINT((ndo, "\n\t\tAFI %s (%u), SAFI %s (%u)",
                           tok2str(af_values, "Unknown",
                                      EXTRACT_16BITS(opt+i+2)),
                           EXTRACT_16BITS(opt+i+2),
                           tok2str(bgp_safi_values, "Unknown",
                                      opt[i+5]),
                           opt[i+5]));
                    break;
                case BGP_CAPCODE_RESTART:
                    /* Restart Flags (4 bits), Restart Time in seconds (12 bits) */
                    ND_TCHECK_16BITS(opt + i + 2);
                    ND_PRINT((ndo, "\n\t\tRestart Flags: [%s], Restart Time %us",
                           ((opt[i+2])&0x80) ? "R" : "none",
                           EXTRACT_16BITS(opt+i+2)&0xfff));
                    tcap_len-=2;
                    cap_offset=4;
                    while(tcap_len>=4) {
                        ND_TCHECK_8BITS(opt + i + cap_offset + 3);
                        ND_PRINT((ndo, "\n\t\t  AFI %s (%u), SAFI %s (%u), Forwarding state preserved: %s",
                               tok2str(af_values,"Unknown",
                                          EXTRACT_16BITS(opt+i+cap_offset)),
                               EXTRACT_16BITS(opt+i+cap_offset),
                               tok2str(bgp_safi_values,"Unknown",
                                          opt[i+cap_offset+2]),
                               opt[i+cap_offset+2],
                               ((opt[i+cap_offset+3])&0x80) ? "yes" : "no" ));
                        tcap_len-=4;
                        cap_offset+=4;
                    }
                    break;
                case BGP_CAPCODE_RR:
                case BGP_CAPCODE_RR_CISCO:
                    break;
                case BGP_CAPCODE_AS_NEW:

                    /*
                     * Extract the 4 byte AS number encoded.
                     */
                    if (cap_len == 4) {
                        ND_PRINT((ndo, "\n\t\t 4 Byte AS %s",
                            as_printf(ndo, astostr, sizeof(astostr),
                            EXTRACT_32BITS(opt + i + 2))));
                    }
                    break;
                case BGP_CAPCODE_ADD_PATH:
                    cap_offset=2;
                    if (tcap_len == 0) {
                        ND_PRINT((ndo, " (bogus)")); /* length */
                        break;
                    }
                    while (tcap_len > 0) {
                        if (tcap_len < 4) {
                            ND_PRINT((ndo, "\n\t\t(invalid)"));
                            break;
                        }
                        ND_PRINT((ndo, "\n\t\tAFI %s (%u), SAFI %s (%u), Send/Receive: %s",
                                  tok2str(af_values,"Unknown",EXTRACT_16BITS(opt+i+cap_offset)),
                                  EXTRACT_16BITS(opt+i+cap_offset),
                                  tok2str(bgp_safi_values,"Unknown",opt[i+cap_offset+2]),
                                  opt[i+cap_offset+2],
                                  tok2str(bgp_add_path_recvsend,"Bogus (0x%02x)",opt[i+cap_offset+3])
                        ));
                        tcap_len-=4;
                        cap_offset+=4;
                    }
                    break;
                default:
                    ND_PRINT((ndo, "\n\t\tno decoder for Capability %u",
                           cap_type));
                    if (ndo->ndo_vflag <= 1)
                        print_unknown_data(ndo, &opt[i+2], "\n\t\t", cap_len);
                    break;
                }
                if (ndo->ndo_vflag > 1 && cap_len > 0) {
                    print_unknown_data(ndo, &opt[i+2], "\n\t\t", cap_len);
                }
                i += BGP_CAP_HEADER_SIZE + cap_len;
        }
        return;

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

Project: tcpdump
File: print-bgp.c

bgp_header_print(netdissect_options *ndo,
                 const u_char *dat, int length)
{
	struct bgp bgp;

	ND_TCHECK2(dat[0], BGP_SIZE);
	memcpy(&bgp, dat, BGP_SIZE);
	ND_PRINT((ndo, "\n\t%s Message (%u), length: %u",
               tok2str(bgp_msg_values, "Unknown", bgp.bgp_type),
               bgp.bgp_type,
               length));

	switch (bgp.bgp_type) {
	case BGP_OPEN:
		bgp_open_print(ndo, dat, length);
		break;
	case BGP_UPDATE:
		bgp_update_print(ndo, dat, length);
		break;
	case BGP_NOTIFICATION:
		bgp_notification_print(ndo, dat, length);
		break;
        case BGP_KEEPALIVE:
                break;
        case BGP_ROUTE_REFRESH:
                bgp_route_refresh_print(ndo, dat, length);
                break;
        default:
                /* we have no decoder for the BGP message */
                ND_TCHECK2(*dat, length);
                ND_PRINT((ndo, "\n\t  no Message %u decoder", bgp.bgp_type));
                print_unknown_data(ndo, dat, "\n\t  ", length);
                break;
	}
	return 1;
trunc:
	ND_PRINT((ndo, "%s", tstr));
	return 0;
}

Project: tcpdump
File: print-bgp.c

bgp_open_print(netdissect_options *ndo,
               const u_char *dat, int length)
{
	struct bgp_open bgpo;
	struct bgp_opt bgpopt;
	const u_char *opt;
	int i;

	ND_TCHECK2(dat[0], BGP_OPEN_SIZE);
	memcpy(&bgpo, dat, BGP_OPEN_SIZE);

	ND_PRINT((ndo, "\n\t  Version %d, ", bgpo.bgpo_version));
	ND_PRINT((ndo, "my AS %s, ",
	    as_printf(ndo, astostr, sizeof(astostr), ntohs(bgpo.bgpo_myas))));
	ND_PRINT((ndo, "Holdtime %us, ", ntohs(bgpo.bgpo_holdtime)));
	ND_PRINT((ndo, "ID %s", ipaddr_string(ndo, &bgpo.bgpo_id)));
	ND_PRINT((ndo, "\n\t  Optional parameters, length: %u", bgpo.bgpo_optlen));

        /* some little sanity checking */
        if (length < bgpo.bgpo_optlen+BGP_OPEN_SIZE)
            return;

	/* ugly! */
	opt = &((const struct bgp_open *)dat)->bgpo_optlen;
	opt++;

	i = 0;
	while (i < bgpo.bgpo_optlen) {
		ND_TCHECK2(opt[i], BGP_OPT_SIZE);
		memcpy(&bgpopt, &opt[i], BGP_OPT_SIZE);
		if (i + 2 + bgpopt.bgpopt_len > bgpo.bgpo_optlen) {
			ND_PRINT((ndo, "\n\t     Option %d, length: %u", bgpopt.bgpopt_type, bgpopt.bgpopt_len));
			break;
		}

		ND_PRINT((ndo, "\n\t    Option %s (%u), length: %u",
		       tok2str(bgp_opt_values,"Unknown",
				  bgpopt.bgpopt_type),
		       bgpopt.bgpopt_type,
		       bgpopt.bgpopt_len));

		/* now let's decode the options we know*/
		switch(bgpopt.bgpopt_type) {

		case BGP_OPT_CAP:
			bgp_capabilities_print(ndo, &opt[i+BGP_OPT_SIZE],
			    bgpopt.bgpopt_len);
			break;

		case BGP_OPT_AUTH:
		default:
		       ND_PRINT((ndo, "\n\t      no decoder for option %u",
			   bgpopt.bgpopt_type));
		       break;
		}
		i += BGP_OPT_SIZE + bgpopt.bgpopt_len;
	}
	return;
trunc:
	ND_PRINT((ndo, "%s", tstr));
}

Project: tcpdump
File: print-bgp.c

bgp_print(netdissect_options *ndo,
          const u_char *dat, int length)
{
	const u_char *p;
	const u_char *ep;
	const u_char *start;
	const u_char marker[] = {
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	};
	struct bgp bgp;
	uint16_t hlen;

	ep = dat + length;
	if (ndo->ndo_snapend < dat + length)
		ep = ndo->ndo_snapend;

	ND_PRINT((ndo, ": BGP"));

        if (ndo->ndo_vflag < 1) /* lets be less chatty */
                return;

	p = dat;
	start = p;
	while (p < ep) {
		if (!ND_TTEST2(p[0], 1))
			break;
		if (p[0] != 0xff) {
			p++;
			continue;
		}

		if (!ND_TTEST2(p[0], sizeof(marker)))
			break;
		if (memcmp(p, marker, sizeof(marker)) != 0) {
			p++;
			continue;
		}

		/* found BGP header */
		ND_TCHECK2(p[0], BGP_SIZE);	/*XXX*/
		memcpy(&bgp, p, BGP_SIZE);

		if (start != p)
			ND_PRINT((ndo, " %s", tstr));

		hlen = ntohs(bgp.bgp_len);
		if (hlen < BGP_SIZE) {
			ND_PRINT((ndo, "\n[|BGP Bogus header length %u < %u]", hlen,
			    BGP_SIZE));
			break;
		}

		if (ND_TTEST2(p[0], hlen)) {
			if (!bgp_header_print(ndo, p, hlen))
				return;
			p += hlen;
			start = p;
		} else {
			ND_PRINT((ndo, "\n[|BGP %s]",
			       tok2str(bgp_msg_values,
					  "Unknown Message Type",
					  bgp.bgp_type)));
			break;
		}
	}

	return;

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

Project: tcpdump
File: print-bgp.c

decode_rt_routing_info(netdissect_options *ndo,
                       const u_char *pptr, char *buf, u_int buflen)
{
	uint8_t route_target[8];
	u_int plen;
	char asbuf[sizeof(astostr)]; /* bgp_vpn_rd_print() overwrites astostr */

	/* NLRI "prefix length" from RFC 2858 Section 4. */
	ND_TCHECK(pptr[0]);
	plen = pptr[0];   /* get prefix length */

	/* NLRI "prefix" (ibid), valid lengths are { 0, 32, 33, ..., 96 } bits.
	 * RFC 4684 Section 4 defines the layout of "origin AS" and "route
	 * target" fields inside the "prefix" depending on its length.
	 */
	if (0 == plen) {
		/* Without "origin AS", without "route target". */
		snprintf(buf, buflen, "default route target");
		return 1;
	}

	if (32 > plen)
		return -1;

	/* With at least "origin AS", possibly with "route target". */
	ND_TCHECK_32BITS(pptr + 1);
	as_printf(ndo, asbuf, sizeof(asbuf), EXTRACT_32BITS(pptr + 1));

        plen-=32; /* adjust prefix length */

	if (64 < plen)
		return -1;

	/* From now on (plen + 7) / 8 evaluates to { 0, 1, 2, ..., 8 }
	 * and gives the number of octets in the variable-length "route
	 * target" field inside this NLRI "prefix". Look for it.
	 */
	memset(&route_target, 0, sizeof(route_target));
	ND_TCHECK2(pptr[5], (plen + 7) / 8);
	memcpy(&route_target, &pptr[5], (plen + 7) / 8);
	/* Which specification says to do this? */
	if (plen % 8) {
		((u_char *)&route_target)[(plen + 7) / 8 - 1] &=
			((0xff00 >> (plen % 8)) & 0xff);
	}
	snprintf(buf, buflen, "origin AS: %s, route target %s",
	    asbuf,
	    bgp_vpn_rd_print(ndo, (u_char *)&route_target));

	return 5 + (plen + 7) / 8;

trunc:
	return -2;
}