Re: [PATCH 1/2] libtraceevent: Add loading of BTF to the tep handle

Steven Rostedt <[email protected]> Mon, 4 Aug 2025 13:34:43 -0400
Newsgroups org.kernel.vger.linux-trace-devel
Message-ID <[email protected]>
On Mon, 4 Aug 2025 17:07:04 +0100
Douglas Raillard <[email protected]> wrote:

> > +struct btf_header;
> > +struct btf_type;
> > +
> > +struct tep_btf {
> > +	struct btf_header	*hdr;
> > +	const char		*strings;
> > +	struct btf_type		**types;
> > +	unsigned long		nr_types;
> > +	struct btf_type		**funcs;
> > +	unsigned long		nr_funcs;
> > +	void			*data;
> > +	size_t			raw_size;
> > +	void			*raw_data;
> > +};
> > +
> > +#define REALLOC_SIZE (1 << 10)
> > +#define REALLOC_MASK (REALLOC_SIZE - 1)
> > +
> > +static const char *btf_name(struct tep_btf *btf, int off)
> > +{
> > +	if (off < btf->hdr->str_len)  
> 
> That assumes the input is well-behaved and str_len is set to the actual length of
> the strings data, but it could be anything. tep_btf_init() should check that
> btf->hdr->strings + btf->hdr->str_len point inside the provided data.

Heh! You know I thought about that and then said to myself "heck, it's user
space, we can let it crash!" ;-)

But yeah, it is a library an we should make it a bit more robust.

> 
> > +		return btf->strings + off;> +	return "";
> > +}
> > +
> > +/* List taken from the Linux kernel */
> > +static const char * const btf_kind_str[NR_BTF_KINDS] = {
> > +	[BTF_KIND_UNKN]		= "UNKNOWN",
> > +	[BTF_KIND_INT]		= "INT",
> > +	[BTF_KIND_PTR]		= "PTR",
> > +	[BTF_KIND_ARRAY]	= "ARRAY",
> > +	[BTF_KIND_STRUCT]	= "STRUCT",
> > +	[BTF_KIND_UNION]	= "UNION",
> > +	[BTF_KIND_ENUM]		= "ENUM",
> > +	[BTF_KIND_FWD]		= "FWD",
> > +	[BTF_KIND_TYPEDEF]	= "TYPEDEF",
> > +	[BTF_KIND_VOLATILE]	= "VOLATILE",
> > +	[BTF_KIND_CONST]	= "CONST",
> > +	[BTF_KIND_RESTRICT]	= "RESTRICT",
> > +	[BTF_KIND_FUNC]		= "FUNC",
> > +	[BTF_KIND_FUNC_PROTO]	= "FUNC_PROTO",
> > +	[BTF_KIND_VAR]		= "VAR",
> > +	[BTF_KIND_DATASEC]	= "DATASEC",
> > +	[BTF_KIND_FLOAT]	= "FLOAT",
> > +	[BTF_KIND_DECL_TAG]	= "DECL_TAG",
> > +	[BTF_KIND_TYPE_TAG]	= "TYPE_TAG",
> > +	[BTF_KIND_ENUM64]	= "ENUM64",
> > +};
> > +


> > +static int load_types(struct tep_btf *btf)
> > +{
> > +	struct btf_type *type;
> > +	void *start, *end;
> > +	int size;
> > +
> > +	start = btf->data + btf->hdr->type_off;
> > +	end = start + btf->hdr->type_len;  
> 
> Those should also be validated.

Yeah, probably.

> 
> > +
> > +	if (end > btf->raw_data + btf->raw_size)
> > +		return -1;
> > +
> > +	for (type = start; (void *)type < end;) {
> > +		if (add_type(btf, type))
> > +			return -1;
> > +		if (BTF_INFO_KIND(type->info) == BTF_KIND_FUNC) {
> > +			if (add_func(btf, type))
> > +				return -1;
> > +		}
> > +		size = btf_type_size(type);
> > +		if (size < 0) {
> > +			tep_warning("Invalid type %d\n", BTF_INFO_KIND(type->info));
> > +			return -1;
> > +		}
> > +		type = (void *)type + size;
> > +	}
> > +


> > +static struct tep_btf *tep_btf_init(void *raw_data, size_t data_size)
> > +{
> > +	struct tep_btf *btf;
> > +
> > +	btf = calloc(1, sizeof(*btf));
> > +	if (!btf)
> > +		return NULL;
> > +	
> > +	btf->raw_data = raw_data;
> > +	btf->raw_size = data_size;
> > +	btf->hdr = raw_data;
> > +	if (btf->hdr->hdr_len < sizeof(*btf->hdr)) {
> > +		tep_warning("Header (%d) smaller than expected header %zd",
> > +			    btf->hdr->hdr_len, sizeof(*btf->hdr));
> > +		goto fail;
> > +	}  
> 
> btf->hdr->magic and btf->hdr->version should be checked to catch any garbage input and
> breaking change to the format:
> 
>    The magic is 0xeB9F, which has different encoding for big and little endian systems,
>    and can be used to test whether BTF is generated for big- or little-endian target.
> 
>    https://docs.kernel.org/bpf/btf.html#btf-type-and-string-encoding
> 

Yep. As I stated above, the thought crossed my mind, and then my laziness
came across and said "nah".

I was more trying to get this code out to the public more than adding it.
But you are correct, it should have these validation checks.

-- Steve


> > +
> > +	btf->data = btf->raw_data + btf->hdr->hdr_len;
> > +
> > +	btf->strings = btf->data + btf->hdr->str_off;  
> 
> This pointer crafting without input validation looks like a CVE-in-waiting.
> Using libbpf would indeed add a dependency, but it seems those checks are taken care of:
> https://github.com/libbpf/libbpf/blob/58dd1f58b57294b2e59482245b29e46f1812b82d/src/btf.c#L258
> 
> This is not to say libbpf is perfect, but there also seem to be some people running fuzzers on it
> so issues of this kind have a higher chance of getting found, e.g.
> https://nvd.nist.gov/vuln/detail/CVE-2025-29481
> 
> I suppose there is no silver bullet here, as adding dependencies to a C program is a pain
> as it breaks downstream build recipes ...
> 
> Alternatively, the Rust parser has no BTF-related buffer issue, but that's because it does not
> implement anything BTF-related yet :)
> 
> > +	printf("str len = %d\n", btf->hdr->str_len);
> > +
> > +	if (load_types(btf) < 0)
> > +		goto fail;
> > +