Commit ec6209c8 authored by Daniel Müller's avatar Daniel Müller Committed by Andrii Nakryiko
Browse files

bpf, libbpf: Add type match support



This patch adds support for the proposed type match relation to
relo_core where it is shared between userspace and kernel. It plumbs
through both kernel-side and libbpf-side support.

The matching relation is defined as follows (copy from source):
- modifiers and typedefs are stripped (and, hence, effectively ignored)
- generally speaking types need to be of same kind (struct vs. struct, union
  vs. union, etc.)
  - exceptions are struct/union behind a pointer which could also match a
    forward declaration of a struct or union, respectively, and enum vs.
    enum64 (see below)
Then, depending on type:
- integers:
  - match if size and signedness match
- arrays & pointers:
  - target types are recursively matched
- structs & unions:
  - local members need to exist in target with the same name
  - for each member we recursively check match unless it is already behind a
    pointer, in which case we only check matching names and compatible kind
- enums:
  - local variants have to have a match in target by symbolic name (but not
    numeric value)
  - size has to match (but enum may match enum64 and vice versa)
- function pointers:
  - number and position of arguments in local type has to match target
  - for each argument and the return value we recursively check match

Signed-off-by: default avatarDaniel Müller <deso@posteo.net>
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20220628160127.607834-5-deso@posteo.net
parent 633e7ceb
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -7443,6 +7443,15 @@ int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
					   MAX_TYPES_ARE_COMPAT_DEPTH);
}

#define MAX_TYPES_MATCH_DEPTH 2

int bpf_core_types_match(const struct btf *local_btf, u32 local_id,
			 const struct btf *targ_btf, u32 targ_id)
{
	return __bpf_core_types_match(local_btf, local_id, targ_btf, targ_id, false,
				      MAX_TYPES_MATCH_DEPTH);
}

static bool bpf_core_is_flavor_sep(const char *s)
{
	/* check X___Y name pattern, where X and Y are not underscores */
+6 −0
Original line number Diff line number Diff line
@@ -5470,6 +5470,12 @@ int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
	return __bpf_core_types_are_compat(local_btf, local_id, targ_btf, targ_id, 32);
}

int bpf_core_types_match(const struct btf *local_btf, __u32 local_id,
			 const struct btf *targ_btf, __u32 targ_id)
{
	return __bpf_core_types_match(local_btf, local_id, targ_btf, targ_id, false, 32);
}

static size_t bpf_core_hash_fn(const void *key, void *ctx)
{
	return (size_t)key;
+275 −4
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ static const char *core_relo_kind_str(enum bpf_core_relo_kind kind)
	case BPF_CORE_TYPE_ID_LOCAL: return "local_type_id";
	case BPF_CORE_TYPE_ID_TARGET: return "target_type_id";
	case BPF_CORE_TYPE_EXISTS: return "type_exists";
	case BPF_CORE_TYPE_MATCHES: return "type_matches";
	case BPF_CORE_TYPE_SIZE: return "type_size";
	case BPF_CORE_ENUMVAL_EXISTS: return "enumval_exists";
	case BPF_CORE_ENUMVAL_VALUE: return "enumval_value";
@@ -123,6 +124,7 @@ static bool core_relo_is_type_based(enum bpf_core_relo_kind kind)
	case BPF_CORE_TYPE_ID_LOCAL:
	case BPF_CORE_TYPE_ID_TARGET:
	case BPF_CORE_TYPE_EXISTS:
	case BPF_CORE_TYPE_MATCHES:
	case BPF_CORE_TYPE_SIZE:
		return true;
	default:
@@ -251,7 +253,7 @@ int __bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
 *   - field 'a' access (corresponds to '2' in low-level spec);
 *   - array element #3 access (corresponds to '3' in low-level spec).
 *
 * Type-based relocations (TYPE_EXISTS/TYPE_SIZE,
 * Type-based relocations (TYPE_EXISTS/TYPE_MATCHES/TYPE_SIZE,
 * TYPE_ID_LOCAL/TYPE_ID_TARGET) don't capture any field information. Their
 * spec and raw_spec are kept empty.
 *
@@ -568,6 +570,11 @@ static int bpf_core_spec_match(struct bpf_core_spec *local_spec,
	targ_spec->relo_kind = local_spec->relo_kind;

	if (core_relo_is_type_based(local_spec->relo_kind)) {
		if (local_spec->relo_kind == BPF_CORE_TYPE_MATCHES)
			return bpf_core_types_match(local_spec->btf,
						    local_spec->root_type_id,
						    targ_btf, targ_id);
		else
			return bpf_core_types_are_compat(local_spec->btf,
							 local_spec->root_type_id,
							 targ_btf, targ_id);
@@ -819,6 +826,7 @@ static int bpf_core_calc_type_relo(const struct bpf_core_relo *relo,
			*validate = false;
		break;
	case BPF_CORE_TYPE_EXISTS:
	case BPF_CORE_TYPE_MATCHES:
		*val = 1;
		break;
	case BPF_CORE_TYPE_SIZE:
@@ -1410,3 +1418,266 @@ int bpf_core_calc_relo_insn(const char *prog_name,

	return 0;
}

static bool bpf_core_names_match(const struct btf *local_btf, size_t local_name_off,
				 const struct btf *targ_btf, size_t targ_name_off)
{
	const char *local_n, *targ_n;
	size_t local_len, targ_len;

	local_n = btf__name_by_offset(local_btf, local_name_off);
	targ_n = btf__name_by_offset(targ_btf, targ_name_off);

	if (str_is_empty(targ_n))
		return str_is_empty(local_n);

	targ_len = bpf_core_essential_name_len(targ_n);
	local_len = bpf_core_essential_name_len(local_n);

	return targ_len == local_len && strncmp(local_n, targ_n, local_len) == 0;
}

static int bpf_core_enums_match(const struct btf *local_btf, const struct btf_type *local_t,
				const struct btf *targ_btf, const struct btf_type *targ_t)
{
	__u16 local_vlen = btf_vlen(local_t);
	__u16 targ_vlen = btf_vlen(targ_t);
	int i, j;

	if (local_t->size != targ_t->size)
		return 0;

	if (local_vlen > targ_vlen)
		return 0;

	/* iterate over the local enum's variants and make sure each has
	 * a symbolic name correspondent in the target
	 */
	for (i = 0; i < local_vlen; i++) {
		bool matched = false;
		__u32 local_n_off, targ_n_off;

		local_n_off = btf_is_enum(local_t) ? btf_enum(local_t)[i].name_off :
						     btf_enum64(local_t)[i].name_off;

		for (j = 0; j < targ_vlen; j++) {
			targ_n_off = btf_is_enum(targ_t) ? btf_enum(targ_t)[j].name_off :
							   btf_enum64(targ_t)[j].name_off;

			if (bpf_core_names_match(local_btf, local_n_off, targ_btf, targ_n_off)) {
				matched = true;
				break;
			}
		}

		if (!matched)
			return 0;
	}
	return 1;
}

static int bpf_core_composites_match(const struct btf *local_btf, const struct btf_type *local_t,
				     const struct btf *targ_btf, const struct btf_type *targ_t,
				     bool behind_ptr, int level)
{
	const struct btf_member *local_m = btf_members(local_t);
	__u16 local_vlen = btf_vlen(local_t);
	__u16 targ_vlen = btf_vlen(targ_t);
	int i, j, err;

	if (local_vlen > targ_vlen)
		return 0;

	/* check that all local members have a match in the target */
	for (i = 0; i < local_vlen; i++, local_m++) {
		const struct btf_member *targ_m = btf_members(targ_t);
		bool matched = false;

		for (j = 0; j < targ_vlen; j++, targ_m++) {
			if (!bpf_core_names_match(local_btf, local_m->name_off,
						  targ_btf, targ_m->name_off))
				continue;

			err = __bpf_core_types_match(local_btf, local_m->type, targ_btf,
						     targ_m->type, behind_ptr, level - 1);
			if (err > 0) {
				matched = true;
				break;
			}
		}

		if (!matched)
			return 0;
	}
	return 1;
}

/* Check that two types "match".
 *
 * The matching relation is defined as follows:
 * - modifiers and typedefs are stripped (and, hence, effectively ignored)
 * - generally speaking types need to be of same kind (struct vs. struct, union
 *   vs. union, etc.)
 *   - exceptions are struct/union behind a pointer which could also match a
 *     forward declaration of a struct or union, respectively, and enum vs.
 *     enum64 (see below)
 * Then, depending on type:
 * - integers:
 *   - match if size and signedness match
 * - arrays & pointers:
 *   - target types are recursively matched
 * - structs & unions:
 *   - local members need to exist in target with the same name
 *   - for each member we recursively check match unless it is already behind a
 *     pointer, in which case we only check matching names and compatible kind
 * - enums:
 *   - local variants have to have a match in target by symbolic name (but not
 *     numeric value)
 *   - size has to match (but enum may match enum64 and vice versa)
 * - function pointers:
 *   - number and position of arguments in local type has to match target
 *   - for each argument and the return value we recursively check match
 */
int __bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
			   __u32 targ_id, bool behind_ptr, int level)
{
	const struct btf_type *local_t, *targ_t;
	int depth = 32; /* max recursion depth */
	__u16 local_k, targ_k;

	if (level <= 0)
		return -EINVAL;

	local_t = btf_type_by_id(local_btf, local_id);
	targ_t = btf_type_by_id(targ_btf, targ_id);

recur:
	depth--;
	if (depth < 0)
		return -EINVAL;

	local_t = skip_mods_and_typedefs(local_btf, local_id, &local_id);
	targ_t = skip_mods_and_typedefs(targ_btf, targ_id, &targ_id);
	if (!local_t || !targ_t)
		return -EINVAL;

	if (!bpf_core_names_match(local_btf, local_t->name_off, targ_btf, targ_t->name_off))
		return 0;

	local_k = btf_kind(local_t);
	targ_k = btf_kind(targ_t);

	switch (local_k) {
	case BTF_KIND_UNKN:
		return local_k == targ_k;
	case BTF_KIND_FWD: {
		bool local_f = BTF_INFO_KFLAG(local_t->info);

		if (behind_ptr) {
			if (local_k == targ_k)
				return local_f == BTF_INFO_KFLAG(targ_t->info);

			/* for forward declarations kflag dictates whether the
			 * target is a struct (0) or union (1)
			 */
			return (targ_k == BTF_KIND_STRUCT && !local_f) ||
			       (targ_k == BTF_KIND_UNION && local_f);
		} else {
			if (local_k != targ_k)
				return 0;

			/* match if the forward declaration is for the same kind */
			return local_f == BTF_INFO_KFLAG(targ_t->info);
		}
	}
	case BTF_KIND_ENUM:
	case BTF_KIND_ENUM64:
		if (!btf_is_any_enum(targ_t))
			return 0;

		return bpf_core_enums_match(local_btf, local_t, targ_btf, targ_t);
	case BTF_KIND_STRUCT:
	case BTF_KIND_UNION:
		if (behind_ptr) {
			bool targ_f = BTF_INFO_KFLAG(targ_t->info);

			if (local_k == targ_k)
				return 1;

			if (targ_k != BTF_KIND_FWD)
				return 0;

			return (local_k == BTF_KIND_UNION) == targ_f;
		} else {
			if (local_k != targ_k)
				return 0;

			return bpf_core_composites_match(local_btf, local_t, targ_btf, targ_t,
							 behind_ptr, level);
		}
	case BTF_KIND_INT: {
		__u8 local_sgn;
		__u8 targ_sgn;

		if (local_k != targ_k)
			return 0;

		local_sgn = btf_int_encoding(local_t) & BTF_INT_SIGNED;
		targ_sgn = btf_int_encoding(targ_t) & BTF_INT_SIGNED;

		return local_t->size == targ_t->size && local_sgn == targ_sgn;
	}
	case BTF_KIND_PTR:
		if (local_k != targ_k)
			return 0;

		behind_ptr = true;

		local_id = local_t->type;
		targ_id = targ_t->type;
		goto recur;
	case BTF_KIND_ARRAY: {
		const struct btf_array *local_array = btf_array(local_t);
		const struct btf_array *targ_array = btf_array(targ_t);

		if (local_k != targ_k)
			return 0;

		if (local_array->nelems != targ_array->nelems)
			return 0;

		local_id = local_array->type;
		targ_id = targ_array->type;
		goto recur;
	}
	case BTF_KIND_FUNC_PROTO: {
		struct btf_param *local_p = btf_params(local_t);
		struct btf_param *targ_p = btf_params(targ_t);
		__u16 local_vlen = btf_vlen(local_t);
		__u16 targ_vlen = btf_vlen(targ_t);
		int i, err;

		if (local_k != targ_k)
			return 0;

		if (local_vlen != targ_vlen)
			return 0;

		for (i = 0; i < local_vlen; i++, local_p++, targ_p++) {
			err = __bpf_core_types_match(local_btf, local_p->type, targ_btf,
						     targ_p->type, behind_ptr, level - 1);
			if (err <= 0)
				return err;
		}

		/* tail recurse for return type check */
		local_id = local_t->type;
		targ_id = targ_t->type;
		goto recur;
	}
	default:
		pr_warn("unexpected kind %s relocated, local [%d], target [%d]\n",
			btf_kind_str(local_t), local_id, targ_id);
		return 0;
	}
}
+4 −0
Original line number Diff line number Diff line
@@ -72,6 +72,10 @@ int __bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
				const struct btf *targ_btf, __u32 targ_id, int level);
int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
			      const struct btf *targ_btf, __u32 targ_id);
int __bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
			   __u32 targ_id, bool behind_ptr, int level);
int bpf_core_types_match(const struct btf *local_btf, __u32 local_id, const struct btf *targ_btf,
			 __u32 targ_id);

size_t bpf_core_essential_name_len(const char *name);