> resolve_btfids: Build separate main and inline BTF objects
TL;DR: In resolve_btfids --inline, extract_inline_btf() may free a base BTF
that the new main BTF still points to, causing a possible use-after-free for
modules with .BTF.base; in-tree builds seem unaffected.
> diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c
> index 37d7e7224207..dcd289d16faf 100644
> --- a/tools/bpf/resolve_btfids/main.c
> +++ b/tools/bpf/resolve_btfids/main.c
[ ... ]
> @@ -1510,6 +1569,85 @@ static int btf2btf(struct object *obj)
> return err;
> }
>
[ ... ]
> +static int extract_inline_btf(struct object *obj)
> +{
> + struct btf *btf = obj->btf, *main_btf, *inline_btf;
> + __u32 type_cnt = btf__type_cnt(btf);
> + __u8 *colors;
> + int err;
> +
> + colors = calloc(type_cnt, sizeof(*colors));
> + if (!colors)
> + return -ENOMEM;
> + err = color_btf(obj, colors);
> + if (err)
> + goto out;
> + err = btf_split_by_color(btf, colors, &main_btf, &inline_btf);
> + if (!err) {
> + obj->btf = main_btf;
> + obj->inline_btf = inline_btf;
> + btf__free(btf);
^^^^
Can this btf__free(btf) free a base BTF that the new obj->btf still points
to?
btf_split_by_color() creates main_btf with .base_btf set to
btf__base_btf(src), and btf_new_empty() stores that pointer without taking
ownership. btf__free() on the source also frees its base when the source
owns it:
if (btf->owns_base)
btf__free(btf->base_btf);
libbpf sets owns_base in btf_parse_elf() when the ELF passed to
btf__parse_split() has a .BTF.base section and no base_btf was given.
This looks reachable with "resolve_btfids --inline mod.ko" (no --btf_base,
no --btf) on an out-of-tree module that already has .BTF, .BTF.base and
.BTF_ids:
load_btf()
btf__parse_split(obj->path, NULL)
owns_base = true, obj->base_btf stays NULL
finalize_btf()
distill is skipped because obj->base_btf is NULL
sort_btf_by_name()
extract_inline_btf()
btf__free(btf)
also frees the distilled base that main_btf->base_btf points to
main()
symbols_resolve()
btf__type_by_id(obj->btf, ...)
btf_type_by_id(btf->base_btf, type_id)
reads freed memory
Without --inline the same command works, because obj->btf keeps owning its
base until main() frees it.
The in-tree build does not appear to be affected, since gen-btf.sh passes a
raw --btf file together with --btf_base, which leaves owns_base false. I did
not find a fix for this later in the series.
Should extract_inline_btf() keep the original BTF alive, for example in a
field of struct object that main() frees after obj->btf? Alternatively,
could it reject or handle the case where btf__base_btf(obj->btf) is set but
obj->base_btf is NULL?
> + }
> +out:
> + free(colors);
> + return err;
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/37149955093