> 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

Reply via email to