diff options
| author | Dmitry Torokhov <[email protected]> | 2022-10-09 22:30:23 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <[email protected]> | 2022-10-09 22:30:23 -0700 | 
| commit | 5f8f8574c7f5585b09a9623f0f13462e4eb67b4d (patch) | |
| tree | 8f1d5e88bf9604a9e39fbcce0e37b3d8cee451bb /tools/lib/bpf/relo_core.c | |
| parent | e62563db857f81d75c5726a35bc0180bed6d1540 (diff) | |
| parent | fe5b6aaef72a0f7daa06e7960e0bee45c2984e41 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.1 merge window.
Diffstat (limited to 'tools/lib/bpf/relo_core.c')
| -rw-r--r-- | tools/lib/bpf/relo_core.c | 479 | 
1 files changed, 431 insertions, 48 deletions
| diff --git a/tools/lib/bpf/relo_core.c b/tools/lib/bpf/relo_core.c index ba4453dfd1ed..c4b0e81ae293 100644 --- a/tools/lib/bpf/relo_core.c +++ b/tools/lib/bpf/relo_core.c @@ -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: @@ -141,6 +143,86 @@ static bool core_relo_is_enumval_based(enum bpf_core_relo_kind kind)  	}  } +int __bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id, +				const struct btf *targ_btf, __u32 targ_id, int level) +{ +	const struct btf_type *local_type, *targ_type; +	int depth = 32; /* max recursion depth */ + +	/* caller made sure that names match (ignoring flavor suffix) */ +	local_type = btf_type_by_id(local_btf, local_id); +	targ_type = btf_type_by_id(targ_btf, targ_id); +	if (!btf_kind_core_compat(local_type, targ_type)) +		return 0; + +recur: +	depth--; +	if (depth < 0) +		return -EINVAL; + +	local_type = skip_mods_and_typedefs(local_btf, local_id, &local_id); +	targ_type = skip_mods_and_typedefs(targ_btf, targ_id, &targ_id); +	if (!local_type || !targ_type) +		return -EINVAL; + +	if (!btf_kind_core_compat(local_type, targ_type)) +		return 0; + +	switch (btf_kind(local_type)) { +	case BTF_KIND_UNKN: +	case BTF_KIND_STRUCT: +	case BTF_KIND_UNION: +	case BTF_KIND_ENUM: +	case BTF_KIND_FWD: +	case BTF_KIND_ENUM64: +		return 1; +	case BTF_KIND_INT: +		/* just reject deprecated bitfield-like integers; all other +		 * integers are by default compatible between each other +		 */ +		return btf_int_offset(local_type) == 0 && btf_int_offset(targ_type) == 0; +	case BTF_KIND_PTR: +		local_id = local_type->type; +		targ_id = targ_type->type; +		goto recur; +	case BTF_KIND_ARRAY: +		local_id = btf_array(local_type)->type; +		targ_id = btf_array(targ_type)->type; +		goto recur; +	case BTF_KIND_FUNC_PROTO: { +		struct btf_param *local_p = btf_params(local_type); +		struct btf_param *targ_p = btf_params(targ_type); +		__u16 local_vlen = btf_vlen(local_type); +		__u16 targ_vlen = btf_vlen(targ_type); +		int i, err; + +		if (local_vlen != targ_vlen) +			return 0; + +		for (i = 0; i < local_vlen; i++, local_p++, targ_p++) { +			if (level <= 0) +				return -EINVAL; + +			skip_mods_and_typedefs(local_btf, local_p->type, &local_id); +			skip_mods_and_typedefs(targ_btf, targ_p->type, &targ_id); +			err = __bpf_core_types_are_compat(local_btf, local_id, targ_btf, targ_id, +							  level - 1); +			if (err <= 0) +				return err; +		} + +		/* tail recurse for return type check */ +		skip_mods_and_typedefs(local_btf, local_type->type, &local_id); +		skip_mods_and_typedefs(targ_btf, targ_type->type, &targ_id); +		goto recur; +	} +	default: +		pr_warn("unexpected kind %s relocated, local [%d], target [%d]\n", +			btf_kind_str(local_type), local_id, targ_id); +		return 0; +	} +} +  /*   * Turn bpf_core_relo into a low- and high-level spec representation,   * validating correctness along the way, as well as calculating resulting @@ -167,11 +249,11 @@ static bool core_relo_is_enumval_based(enum bpf_core_relo_kind kind)   * just a parsed access string representation): [0, 1, 2, 3].   *   * High-level spec will capture only 3 points: - *   - intial zero-index access by pointer (&s->... is the same as &s[0]...); + *   - initial zero-index access by pointer (&s->... is the same as &s[0]...);   *   - 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.   * @@ -186,7 +268,7 @@ int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,  	struct bpf_core_accessor *acc;  	const struct btf_type *t;  	const char *name, *spec_str; -	__u32 id; +	__u32 id, name_off;  	__s64 sz;  	spec_str = btf__name_by_offset(btf, relo->access_str_off); @@ -231,11 +313,13 @@ int bpf_core_parse_spec(const char *prog_name, const struct btf *btf,  	spec->len++;  	if (core_relo_is_enumval_based(relo->kind)) { -		if (!btf_is_enum(t) || spec->raw_len > 1 || access_idx >= btf_vlen(t)) +		if (!btf_is_any_enum(t) || spec->raw_len > 1 || access_idx >= btf_vlen(t))  			return -EINVAL;  		/* record enumerator name in a first accessor */ -		acc->name = btf__name_by_offset(btf, btf_enum(t)[access_idx].name_off); +		name_off = btf_is_enum(t) ? btf_enum(t)[access_idx].name_off +					  : btf_enum64(t)[access_idx].name_off; +		acc->name = btf__name_by_offset(btf, name_off);  		return 0;  	} @@ -340,7 +424,7 @@ recur:  	if (btf_is_composite(local_type) && btf_is_composite(targ_type))  		return 1; -	if (btf_kind(local_type) != btf_kind(targ_type)) +	if (!btf_kind_core_compat(local_type, targ_type))  		return 0;  	switch (btf_kind(local_type)) { @@ -348,6 +432,7 @@ recur:  	case BTF_KIND_FLOAT:  		return 1;  	case BTF_KIND_FWD: +	case BTF_KIND_ENUM64:  	case BTF_KIND_ENUM: {  		const char *local_name, *targ_name;  		size_t local_len, targ_len; @@ -477,6 +562,7 @@ static int bpf_core_spec_match(struct bpf_core_spec *local_spec,  	const struct bpf_core_accessor *local_acc;  	struct bpf_core_accessor *targ_acc;  	int i, sz, matched; +	__u32 name_off;  	memset(targ_spec, 0, sizeof(*targ_spec));  	targ_spec->btf = targ_btf; @@ -484,9 +570,14 @@ 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)) { -		return bpf_core_types_are_compat(local_spec->btf, -						 local_spec->root_type_id, -						 targ_btf, targ_id); +		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);  	}  	local_acc = &local_spec->spec[0]; @@ -494,18 +585,22 @@ static int bpf_core_spec_match(struct bpf_core_spec *local_spec,  	if (core_relo_is_enumval_based(local_spec->relo_kind)) {  		size_t local_essent_len, targ_essent_len; -		const struct btf_enum *e;  		const char *targ_name;  		/* has to resolve to an enum */  		targ_type = skip_mods_and_typedefs(targ_spec->btf, targ_id, &targ_id); -		if (!btf_is_enum(targ_type)) +		if (!btf_is_any_enum(targ_type))  			return 0;  		local_essent_len = bpf_core_essential_name_len(local_acc->name); -		for (i = 0, e = btf_enum(targ_type); i < btf_vlen(targ_type); i++, e++) { -			targ_name = btf__name_by_offset(targ_spec->btf, e->name_off); +		for (i = 0; i < btf_vlen(targ_type); i++) { +			if (btf_is_enum(targ_type)) +				name_off = btf_enum(targ_type)[i].name_off; +			else +				name_off = btf_enum64(targ_type)[i].name_off; + +			targ_name = btf__name_by_offset(targ_spec->btf, name_off);  			targ_essent_len = bpf_core_essential_name_len(targ_name);  			if (targ_essent_len != local_essent_len)  				continue; @@ -583,7 +678,7 @@ static int bpf_core_spec_match(struct bpf_core_spec *local_spec,  static int bpf_core_calc_field_relo(const char *prog_name,  				    const struct bpf_core_relo *relo,  				    const struct bpf_core_spec *spec, -				    __u32 *val, __u32 *field_sz, __u32 *type_id, +				    __u64 *val, __u32 *field_sz, __u32 *type_id,  				    bool *validate)  {  	const struct bpf_core_accessor *acc; @@ -680,8 +775,7 @@ static int bpf_core_calc_field_relo(const char *prog_name,  		*val = byte_sz;  		break;  	case BPF_CORE_FIELD_SIGNED: -		/* enums will be assumed unsigned */ -		*val = btf_is_enum(mt) || +		*val = (btf_is_any_enum(mt) && BTF_INFO_KFLAG(mt->info)) ||  		       (btf_int_encoding(mt) & BTF_INT_SIGNED);  		if (validate)  			*validate = true; /* signedness is never ambiguous */ @@ -708,7 +802,7 @@ static int bpf_core_calc_field_relo(const char *prog_name,  static int bpf_core_calc_type_relo(const struct bpf_core_relo *relo,  				   const struct bpf_core_spec *spec, -				   __u32 *val, bool *validate) +				   __u64 *val, bool *validate)  {  	__s64 sz; @@ -732,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: @@ -751,10 +846,9 @@ static int bpf_core_calc_type_relo(const struct bpf_core_relo *relo,  static int bpf_core_calc_enumval_relo(const struct bpf_core_relo *relo,  				      const struct bpf_core_spec *spec, -				      __u32 *val) +				      __u64 *val)  {  	const struct btf_type *t; -	const struct btf_enum *e;  	switch (relo->kind) {  	case BPF_CORE_ENUMVAL_EXISTS: @@ -764,8 +858,10 @@ static int bpf_core_calc_enumval_relo(const struct bpf_core_relo *relo,  		if (!spec)  			return -EUCLEAN; /* request instruction poisoning */  		t = btf_type_by_id(spec->btf, spec->spec[0].type_id); -		e = btf_enum(t) + spec->spec[0].idx; -		*val = e->val; +		if (btf_is_enum(t)) +			*val = btf_enum(t)[spec->spec[0].idx].val; +		else +			*val = btf_enum64_value(btf_enum64(t) + spec->spec[0].idx);  		break;  	default:  		return -EOPNOTSUPP; @@ -929,7 +1025,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn,  			int insn_idx, const struct bpf_core_relo *relo,  			int relo_idx, const struct bpf_core_relo_res *res)  { -	__u32 orig_val, new_val; +	__u64 orig_val, new_val;  	__u8 class;  	class = BPF_CLASS(insn->code); @@ -954,28 +1050,30 @@ poison:  		if (BPF_SRC(insn->code) != BPF_K)  			return -EINVAL;  		if (res->validate && insn->imm != orig_val) { -			pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %u, exp %u -> %u\n", +			pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %u, exp %llu -> %llu\n",  				prog_name, relo_idx, -				insn_idx, insn->imm, orig_val, new_val); +				insn_idx, insn->imm, (unsigned long long)orig_val, +				(unsigned long long)new_val);  			return -EINVAL;  		}  		orig_val = insn->imm;  		insn->imm = new_val; -		pr_debug("prog '%s': relo #%d: patched insn #%d (ALU/ALU64) imm %u -> %u\n", +		pr_debug("prog '%s': relo #%d: patched insn #%d (ALU/ALU64) imm %llu -> %llu\n",  			 prog_name, relo_idx, insn_idx, -			 orig_val, new_val); +			 (unsigned long long)orig_val, (unsigned long long)new_val);  		break;  	case BPF_LDX:  	case BPF_ST:  	case BPF_STX:  		if (res->validate && insn->off != orig_val) { -			pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDX/ST/STX) value: got %u, exp %u -> %u\n", -				prog_name, relo_idx, insn_idx, insn->off, orig_val, new_val); +			pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDX/ST/STX) value: got %u, exp %llu -> %llu\n", +				prog_name, relo_idx, insn_idx, insn->off, (unsigned long long)orig_val, +				(unsigned long long)new_val);  			return -EINVAL;  		}  		if (new_val > SHRT_MAX) { -			pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) value too big: %u\n", -				prog_name, relo_idx, insn_idx, new_val); +			pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) value too big: %llu\n", +				prog_name, relo_idx, insn_idx, (unsigned long long)new_val);  			return -ERANGE;  		}  		if (res->fail_memsz_adjust) { @@ -987,8 +1085,9 @@ poison:  		orig_val = insn->off;  		insn->off = new_val; -		pr_debug("prog '%s': relo #%d: patched insn #%d (LDX/ST/STX) off %u -> %u\n", -			 prog_name, relo_idx, insn_idx, orig_val, new_val); +		pr_debug("prog '%s': relo #%d: patched insn #%d (LDX/ST/STX) off %llu -> %llu\n", +			 prog_name, relo_idx, insn_idx, (unsigned long long)orig_val, +			 (unsigned long long)new_val);  		if (res->new_sz != res->orig_sz) {  			int insn_bytes_sz, insn_bpf_sz; @@ -1024,20 +1123,20 @@ poison:  			return -EINVAL;  		} -		imm = insn[0].imm + ((__u64)insn[1].imm << 32); +		imm = (__u32)insn[0].imm | ((__u64)insn[1].imm << 32);  		if (res->validate && imm != orig_val) { -			pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDIMM64) value: got %llu, exp %u -> %u\n", +			pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDIMM64) value: got %llu, exp %llu -> %llu\n",  				prog_name, relo_idx,  				insn_idx, (unsigned long long)imm, -				orig_val, new_val); +				(unsigned long long)orig_val, (unsigned long long)new_val);  			return -EINVAL;  		}  		insn[0].imm = new_val; -		insn[1].imm = 0; /* currently only 32-bit values are supported */ -		pr_debug("prog '%s': relo #%d: patched insn #%d (LDIMM64) imm64 %llu -> %u\n", +		insn[1].imm = new_val >> 32; +		pr_debug("prog '%s': relo #%d: patched insn #%d (LDIMM64) imm64 %llu -> %llu\n",  			 prog_name, relo_idx, insn_idx, -			 (unsigned long long)imm, new_val); +			 (unsigned long long)imm, (unsigned long long)new_val);  		break;  	}  	default: @@ -1057,7 +1156,6 @@ poison:  int bpf_core_format_spec(char *buf, size_t buf_sz, const struct bpf_core_spec *spec)  {  	const struct btf_type *t; -	const struct btf_enum *e;  	const char *s;  	__u32 type_id;  	int i, len = 0; @@ -1086,10 +1184,23 @@ int bpf_core_format_spec(char *buf, size_t buf_sz, const struct bpf_core_spec *s  	if (core_relo_is_enumval_based(spec->relo_kind)) {  		t = skip_mods_and_typedefs(spec->btf, type_id, NULL); -		e = btf_enum(t) + spec->raw_spec[0]; -		s = btf__name_by_offset(spec->btf, e->name_off); +		if (btf_is_enum(t)) { +			const struct btf_enum *e; +			const char *fmt_str; + +			e = btf_enum(t) + spec->raw_spec[0]; +			s = btf__name_by_offset(spec->btf, e->name_off); +			fmt_str = BTF_INFO_KFLAG(t->info) ? "::%s = %d" : "::%s = %u"; +			append_buf(fmt_str, s, e->val); +		} else { +			const struct btf_enum64 *e; +			const char *fmt_str; -		append_buf("::%s = %u", s, e->val); +			e = btf_enum64(t) + spec->raw_spec[0]; +			s = btf__name_by_offset(spec->btf, e->name_off); +			fmt_str = BTF_INFO_KFLAG(t->info) ? "::%s = %lld" : "::%s = %llu"; +			append_buf(fmt_str, s, (unsigned long long)btf_enum64_value(e)); +		}  		return len;  	} @@ -1148,11 +1259,11 @@ int bpf_core_format_spec(char *buf, size_t buf_sz, const struct bpf_core_spec *s   * 3. It is supported and expected that there might be multiple flavors   *    matching the spec. As long as all the specs resolve to the same set of   *    offsets across all candidates, there is no error. If there is any - *    ambiguity, CO-RE relocation will fail. This is necessary to accomodate - *    imprefection of BTF deduplication, which can cause slight duplication of + *    ambiguity, CO-RE relocation will fail. This is necessary to accommodate + *    imperfection of BTF deduplication, which can cause slight duplication of   *    the same BTF type, if some directly or indirectly referenced (by   *    pointer) type gets resolved to different actual types in different - *    object files. If such situation occurs, deduplicated BTF will end up + *    object files. If such a situation occurs, deduplicated BTF will end up   *    with two (or more) structurally identical types, which differ only in   *    types they refer to through pointer. This should be OK in most cases and   *    is not an error. @@ -1261,10 +1372,12 @@ int bpf_core_calc_relo_insn(const char *prog_name,  			 * decision and value, otherwise it's dangerous to  			 * proceed due to ambiguity  			 */ -			pr_warn("prog '%s': relo #%d: relocation decision ambiguity: %s %u != %s %u\n", +			pr_warn("prog '%s': relo #%d: relocation decision ambiguity: %s %llu != %s %llu\n",  				prog_name, relo_idx, -				cand_res.poison ? "failure" : "success", cand_res.new_val, -				targ_res->poison ? "failure" : "success", targ_res->new_val); +				cand_res.poison ? "failure" : "success", +				(unsigned long long)cand_res.new_val, +				targ_res->poison ? "failure" : "success", +				(unsigned long long)targ_res->new_val);  			return -EINVAL;  		} @@ -1305,3 +1418,273 @@ 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) +				return err; +			if (err > 0) { +				matched = true; +				break; +			} +		} + +		if (!matched) +			return 0; +	} +	return 1; +} + +/* Check that two types "match". This function assumes that root types were + * already checked for name 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; + +	/* While the name check happens after typedefs are skipped, root-level +	 * typedefs would still be name-matched as that's the contract with +	 * callers. +	 */ +	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; +	} +} |