On 03/10/2026 21:27, [email protected] wrote: >> 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; >> +} > > [ ... ] > >
legit issue; we need to keep around obj.orig_btf for after coloring to be safe. Also worth centralizing all the freeing of btfs in the main out: path to remove a bunch of goto labels and adding a comment to note the importance of btf__free() order. Will fix..
