[BACK]Return to flowspec.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / bgpd

File: [local] / src / usr.sbin / bgpd / flowspec.c (download)

Revision 1.5, Mon Oct 23 13:07:44 2023 UTC (7 months, 1 week ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.4: +2 -2 lines

Use xoff instead of *olen in the shift_right() call. xoff is the right
value here since *olen is (conditionally) set a few lines later to the
same value as xoff.

Should fix the CodeQL warnings because *olen is dereferenced without
proper NULL check.

OK tb@

/*	$OpenBSD: flowspec.c,v 1.5 2023/10/23 13:07:44 claudio Exp $ */

/*
 * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <string.h>
#include <stdio.h>

#include "bgpd.h"
#include "rde.h"

/*
 * Extract the next component from a flowspec NLRI buffer.
 * Returns the length of the component including type field or -1 on failure.
 * Also checks that the prefix encoding is valid.
 */
static int
flowspec_next_component(const uint8_t *buf, int len, int is_v6, int *type)
{
	int vlen = 0;
	uint8_t plen, off, op;

	*type = 0;
	if (len < 1)
		return -1;
	*type = buf[vlen];
	vlen++;
	if (*type < FLOWSPEC_TYPE_MIN || *type >= FLOWSPEC_TYPE_MAX)
		return -1;

	switch (*type) {
	case FLOWSPEC_TYPE_DEST:
	case FLOWSPEC_TYPE_SOURCE:
		if (!is_v6) {
			/* regular RFC 4271 encoding of prefixes */
			if (len < vlen + 1)
				return -1;
			plen = buf[vlen];
			vlen += PREFIX_SIZE(plen);
			if (plen > 32 || len < vlen)
				return -1;
		} else {
			/* special RFC 8956 encoding with extra offset */
			if (len < vlen + 2)
				return -1;
			plen = buf[vlen];
			off = buf[vlen + 1];
			if (plen > 128 || off >= plen)
				return -1;
			vlen += PREFIX_SIZE(plen - off) + 1; /* off is extra */
			if (len < vlen)
				return -1;
		}
		break;
	case FLOWSPEC_TYPE_FLOW:
		if (!is_v6)
			return -1;
		/* FALLTHROUGH */
	default:
		do {
			if (len < vlen + 1)
				return -1;
			op = buf[vlen];
			/* first component cannot have and flag set */
			if (vlen == 1 && op & FLOWSPEC_OP_AND)
				return -1;
			vlen += FLOWSPEC_OP_LEN(op) + 1;

			if (len < vlen)
				return -1;
		} while ((op & FLOWSPEC_OP_EOL) == 0);
		break;
	}
	return vlen;
}

#define MINIMUM(a, b)	((a) < (b) ? (a) : (b))

/*
 * Compare two IPv4 flowspec prefix components.
 * Returns -1 if first prefix is preferred, 1 if second, 0 if equal.
 */
static int
flowspec_cmp_prefix4(const uint8_t *abuf, int ablen, const uint8_t *bbuf,
    int bblen)
{
	uint8_t a[4] = { 0 }, b[4] = { 0 };
	int alen, blen, clen, cmp;

	alen = abuf[1];
	blen = bbuf[1];
	clen = MINIMUM(alen, blen);

	/* only extract the common prefix */
	extract_prefix(abuf + 2, ablen - 2, &a, clen, sizeof(a));
	extract_prefix(bbuf + 2, bblen - 2, &b, clen, sizeof(b));

	/* lowest IP value has precedence */
	cmp = memcmp(a, b, sizeof(a));
	if (cmp < 0)
		return -1;
	if (cmp > 0)
		return 1;

	/* if common prefix, more specific route has precedence */
	if (alen > blen)
		return -1;
	if (alen < blen)
		return 1;
	return 0;
}

/*
 * Compare two IPv6 flowspec prefix components.
 * Returns 1 if first prefix is preferred, -1 if second, 0 if equal.
 * As usual the encoding of IPv6 addresses is extra complex.
 */
static int
flowspec_cmp_prefix6(const uint8_t *abuf, int ablen, const uint8_t *bbuf,
    int bblen)
{
	uint8_t a[16] = { 0 }, b[16] = { 0 };
	int alen, blen, clen, cmp;

	/* lowest offset has precedence */
	if (abuf[2] < bbuf[2])
		return -1;
	if (abuf[2] > bbuf[2])
		return 1;

	/* calculate actual pattern size (len - offset) */
	alen = abuf[1] - abuf[2];
	blen = bbuf[1] - bbuf[2];
	clen = MINIMUM(alen, blen);

	/* only extract the common prefix */
	extract_prefix(abuf + 3, ablen - 3, &a, clen, sizeof(a));
	extract_prefix(bbuf + 3, bblen - 3, &b, clen, sizeof(b));

	/* lowest IP value has precedence */
	cmp = memcmp(a, b, sizeof(a));
	if (cmp < 0)
		return -1;
	if (cmp > 0)
		return 1;

	/* if common prefix, more specific route has precedence */
	if (alen > blen)
		return -1;
	if (alen < blen)
		return 1;
	return 0;
}

/*
 * Check if the flowspec NLRI is syntactically valid.
 */
int
flowspec_valid(const uint8_t *buf, int len, int is_v6)
{
	int l, type, prev = 0;

	/* empty NLRI is invalid */
	if (len == 0)
		return -1;

	while (len > 0) {
		l = flowspec_next_component(buf, len, is_v6, &type);
		if (l == -1)
			return -1;
		/* ensure that types are ordered */
		if (prev >= type)
			return -1;
		prev = type;
		buf += l;
		len -= l;
	}
	if (len < 0)
		return -1;
	return 0;
}

/*
 * Compare two valid flowspec NLRI objects according to RFC 8955 & 8956.
 * Returns -1 if the first object has preference, 1 if not, and 0 if the
 * two objects are equal.
 */
int
flowspec_cmp(const uint8_t *a, int alen, const uint8_t *b, int blen, int is_v6)
{
	int atype, btype;
	int acomplen, bcomplen;
	int cmp;

	while (alen > 0 && blen > 0) {
		acomplen = flowspec_next_component(a, alen, is_v6, &atype);
		bcomplen = flowspec_next_component(b, blen, is_v6, &btype);
		/* should not happen */
		if (acomplen == -1)
			return 1;
		if (bcomplen == -1)
			return -1;

		/* If types differ, lowest type wins. */
		if (atype < btype)
			return -1;
		if (atype > btype)
			return 1;

		switch (atype) {
		case FLOWSPEC_TYPE_DEST:
		case FLOWSPEC_TYPE_SOURCE:
			if (!is_v6) {
				cmp = flowspec_cmp_prefix4(a, acomplen,
				    b, bcomplen);
			} else {
				cmp = flowspec_cmp_prefix6(a, acomplen,
				    b, bcomplen);
			}
			if (cmp != 0)
				return cmp;
			break;
		default:
			cmp = memcmp(a, b, MINIMUM(acomplen, bcomplen));
			/*
			 * Lowest common component prefix wins also
			 * if both commponents are same length also lowest
			 * string has precedence.
			 */
			if (cmp < 0)
				return -1;
			if (cmp > 0)
				return 1;
			/*
			 * Longest component wins when common prefix is equal.
			 * This is not really possible because EOL encoding will
			 * always tie break on the memcmp but the RFC mandates
			 * it (and it is cheap).
			 */
			if (acomplen > bcomplen)
				return -1;
			if (acomplen < bcomplen)
				return 1;
			break;
		}
		a += acomplen;
		alen -= acomplen;
		b += bcomplen;
		blen -= bcomplen;

		/* Rule with more components wins */
		if (alen > 0 && blen <= 0)
			return -1;
		if (alen <= 0 && blen > 0)
			return 1;
	}
	return 0;
}

static void
shift_right(uint8_t *dst, const uint8_t *src, int off, int len)
{
	uint8_t carry = 0;
	int i;

	dst += off / 8;		/* go to inital start point */
	off %= 8;
	len = (len + 7) / 8;	/* how much to copy in bytes */

	for (i = 0; i < len; i++) {
		dst[i] = carry | src[i] >> off;
		if (off != 0)
			carry = src[i] << (8 - off);
	}
	dst[i] = carry;
}

/*
 * Extract a flowspec component and return its buffer and size.
 * Returns 1 on success, 0 if component is not present and -1 on error.
 */
int
flowspec_get_component(const uint8_t *flow, int flowlen, int type, int is_v6,
    const uint8_t **buf, int *len)
{
	int complen, t;

	*buf = NULL;
	*len = 0;

	do {
		complen = flowspec_next_component(flow, flowlen, is_v6, &t);
		if (complen == -1)
			return -1;
		if (type == t)
			break;
		if (type < t)
			return 0;

		flow += complen;
		flowlen -= complen;
	} while (1);

	*buf = flow + 1;
	*len = complen - 1;

	return 1;
}

/*
 * Extract source or destination address into provided bgpd_addr.
 * Returns 1 on success, 0 if no address was present, -1 on error.
 * Sets plen to the prefix len and olen to the offset for IPv6 case.
 * If olen is set to NULL when an IPv6 prefix with offset is fetched
 * the function will return -1.
 */
int
flowspec_get_addr(const uint8_t *flow, int flowlen, int type, int is_v6,
    struct bgpd_addr *addr, uint8_t *plen, uint8_t *olen)
{
	const uint8_t *comp;
	int complen, rv;

	memset(addr, 0, sizeof(*addr));
	*plen = 0;
	if (olen != NULL)
		*olen = 0;

	rv = flowspec_get_component(flow, flowlen, type, is_v6,
	    &comp, &complen);
	if (rv != 1)
		return rv;

	/* flowspec_get_component only returns valid encodings */
	if (!is_v6) {
		addr->aid = AID_INET;
		if (extract_prefix(comp + 1, complen - 1, &addr->v4, comp[0],
		    sizeof(addr->v4)) == -1)
			return -1;
		*plen = comp[0];
	} else {
		uint8_t buf[16] = { 0 };
		int xlen, xoff = 0;

		addr->aid = AID_INET6;
		xlen = comp[0];
		if (comp[1] != 0) {
			if (olen == NULL)
				return -1;
			xoff = comp[1];
			xlen -= xoff;
		}
		if (extract_prefix(comp + 2, complen - 2, buf, xlen,
		    sizeof(buf)) == -1)
			return -1;
		shift_right(addr->v6.s6_addr, buf, xoff, xlen);
		*plen = comp[0];
		if (olen != NULL)
			*olen = comp[1];
	}

	return 1;
}

const char *
flowspec_fmt_label(int type)
{
	switch (type) {
	case FLOWSPEC_TYPE_DEST:
		return "to";
	case FLOWSPEC_TYPE_SOURCE:
		return "from";
	case FLOWSPEC_TYPE_PROTO:
		return "proto";
	case FLOWSPEC_TYPE_PORT:
	case FLOWSPEC_TYPE_DST_PORT:
	case FLOWSPEC_TYPE_SRC_PORT:
		return "port";
	case FLOWSPEC_TYPE_ICMP_TYPE:
		return "icmp-type";
	case FLOWSPEC_TYPE_ICMP_CODE:
		return "icmp-code";
	case FLOWSPEC_TYPE_TCP_FLAGS:
		return "flags";
	case FLOWSPEC_TYPE_PKT_LEN:
		return "length";
	case FLOWSPEC_TYPE_DSCP:
		return "tos";
	case FLOWSPEC_TYPE_FRAG:
		return "fragment";
	case FLOWSPEC_TYPE_FLOW:
		return "flow";
	default:
		return "???";
	}
}

static uint64_t
extract_val(const uint8_t *comp, int len)
{
	uint64_t val = 0;

	while (len-- > 0) {
		val <<= 8;
		val |= *comp++;
	}
	return val;
}

const char *
flowspec_fmt_num_op(const uint8_t *comp, int complen, int *off)
{
	static char buf[32];
	uint64_t val, val2 = 0;
	uint8_t op, op2 = 0;
	int len, len2 = 0;

	if (*off == -1)
		return "";
	if (complen < *off + 1)
		return "bad encoding";

	op = comp[*off];
	len = FLOWSPEC_OP_LEN(op) + 1;
	if (complen < *off + len)
		return "bad encoding";
	val = extract_val(comp + *off + 1, FLOWSPEC_OP_LEN(op));

	if ((op & FLOWSPEC_OP_EOL) == 0) {
		if (complen < *off + len + 1)
			return "bad encoding";
		op2 = comp[*off + len];
		/*
		 * Check if this is a range specification else fall back
		 * to basic rules.
		 */
		if (op2 & FLOWSPEC_OP_AND &&
		    (op & FLOWSPEC_OP_NUM_MASK) == FLOWSPEC_OP_NUM_GE &&
		    (op2 & FLOWSPEC_OP_NUM_MASK) == FLOWSPEC_OP_NUM_LE) {
			len2 =  FLOWSPEC_OP_LEN(op2) + 1;
			val2 = extract_val(comp + *off + len + 1,
			    FLOWSPEC_OP_LEN(op2));
		} else
			op2 = 0;
	}

	if (op2 & FLOWSPEC_OP_AND) {
		/* binary range operation */
		snprintf(buf, sizeof(buf), "%llu - %llu",
		    (unsigned long long)val, (unsigned long long)val2);
	} else {
		/* unary operation */
		switch (op & FLOWSPEC_OP_NUM_MASK) {
		case 0:
			snprintf(buf, sizeof(buf), "%sfalse",
			    op & FLOWSPEC_OP_AND ? "&& " : "");
			break;
		case FLOWSPEC_OP_NUM_EQ:
			snprintf(buf, sizeof(buf), "%s%llu",
			    op & FLOWSPEC_OP_AND ? "&& " : "",
			    (unsigned long long)val);
			break;
		case FLOWSPEC_OP_NUM_GT:
			snprintf(buf, sizeof(buf), "%s> %llu",
			    op & FLOWSPEC_OP_AND ? "&& " : "",
			    (unsigned long long)val);
			break;
		case FLOWSPEC_OP_NUM_GE:
			snprintf(buf, sizeof(buf), "%s>= %llu",
			    op & FLOWSPEC_OP_AND ? "&& " : "",
			    (unsigned long long)val);
			break;
		case FLOWSPEC_OP_NUM_LT:
			snprintf(buf, sizeof(buf), "%s< %llu",
			    op & FLOWSPEC_OP_AND ? "&& " : "",
			    (unsigned long long)val);
			break;
		case FLOWSPEC_OP_NUM_LE:
			snprintf(buf, sizeof(buf), "%s<= %llu",
			    op & FLOWSPEC_OP_AND ? "&& " : "",
			    (unsigned long long)val);
			break;
		case FLOWSPEC_OP_NUM_NOT:
			snprintf(buf, sizeof(buf), "%s!= %llu",
			    op & FLOWSPEC_OP_AND ? "&& " : "",
			    (unsigned long long)val);
			break;
		case 0x7:
			snprintf(buf, sizeof(buf), "%strue",
			    op & FLOWSPEC_OP_AND ? "&& " : "");
			break;
		}
	}

	if (op2 & FLOWSPEC_OP_EOL || op & FLOWSPEC_OP_EOL)
		*off = -1;
	else
		*off += len + len2;

	return buf;
}

static const char *
fmt_flags(uint64_t val, const char *bits, char *buf, size_t blen)
{
	int i, bi;

	for (i = 0, bi = 0; i < 64 && val != 0; i++) {
		if (val & 1) {
			if (bits[i] == '\0' || bits[i] == ' ')
				goto fail;
			buf[bi++] = bits[i];
		}
		val >>= 1;
	}
	buf[bi++] = '\0';
	return buf;

fail:
	snprintf(buf, blen, "%llx", (unsigned long long)val);
	return buf;
}

const char *
flowspec_fmt_bin_op(const uint8_t *comp, int complen, int *off,
    const char *bits)
{
	static char buf[36], bit[17], mask[17];
	uint64_t val, val2 = 0;
	uint8_t op, op2 = 0;
	int len, len2 = 0;

	if (*off == -1)
		return "";
	if (complen < *off + 1)
		return "bad encoding";

	op = comp[*off];
	len = FLOWSPEC_OP_LEN(op) + 1;
	if (complen < *off + len)
		return "bad encoding";
	val = extract_val(comp + *off + 1, FLOWSPEC_OP_LEN(op));

	if ((op & FLOWSPEC_OP_EOL) == 0) {
		if (complen < *off + len + 1)
			return "bad encoding";
		op2 = comp[*off + len];
		/*
		 * Check if this is a mask specification else fall back
		 * to basic rules.
		 */
		if (op2 & FLOWSPEC_OP_AND &&
		    (op & FLOWSPEC_OP_BIT_MASK) == FLOWSPEC_OP_BIT_MATCH &&
		    (op2 & FLOWSPEC_OP_BIT_MASK) == FLOWSPEC_OP_BIT_NOT) {
			len2 =  FLOWSPEC_OP_LEN(op2) + 1;
			val2 = extract_val(comp + *off + len + 1,
			    FLOWSPEC_OP_LEN(op2));
		} else
			op2 = 0;
	}

	if (op2 & FLOWSPEC_OP_AND) {
		val2 |= val;
		snprintf(buf, sizeof(buf), "%s / %s",
		    fmt_flags(val, bits, bit, sizeof(bit)),
		    fmt_flags(val2, bits, mask, sizeof(mask)));
	} else {
		switch (op & FLOWSPEC_OP_BIT_MASK) {
		case 0:
			snprintf(buf, sizeof(buf), "%s",
			    fmt_flags(val, bits, bit, sizeof(bit)));
			break;
		case FLOWSPEC_OP_BIT_NOT:
			snprintf(buf, sizeof(buf), "/ %s",
			    fmt_flags(val, bits, mask, sizeof(mask)));
			break;
		case FLOWSPEC_OP_BIT_MATCH:
			snprintf(buf, sizeof(buf), "%s / %s",
			    fmt_flags(val, bits, bit, sizeof(bit)),
			    fmt_flags(val, bits, mask, sizeof(mask)));
			break;
		case FLOWSPEC_OP_BIT_NOT | FLOWSPEC_OP_BIT_MATCH:
			snprintf(buf, sizeof(buf), "???");
			break;
		}
	}

	if (op2 & FLOWSPEC_OP_EOL || op & FLOWSPEC_OP_EOL)
		*off = -1;
	else
		*off += len + len2;

	return buf;
}