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..


Reply via email to