btf__add_btf() currently rejects split BTF sources with -ENOTSUP. This prevents merging types from multiple kernel module BTFs that are all split against the same vmlinux base. Extend btf__add_btf() to handle split BTF sources by: - Replacing the blanket -ENOTSUP with a validation that src and dst share the same base BTF pointer when both are split. - Computing src_start_id from the source's base to distinguish base type ID references (which must remain unchanged) from split type IDs (which must be remapped to new positions in the destination). - Using src_btf->nr_types instead of btf__type_cnt()-1 for the type count, which is correct for both split and non-split sources. - Pre-emptively calling btf_ensure_modifiable() on the destination's base BTF to prevent a use-after-free: btf_rewrite_str() resolves strings via btf__str_by_offset(src) which may return pointers into the shared base's string data; btf__add_str(dst) then calls btf__find_str(base) which can trigger btf_ensure_modifiable(base), reallocating that string data and invalidating the pointer. For non-split sources the behavior is identical: src_start_id is 1, the type_id < 1 guard is never true (VOID is already skipped), and the remapping formula reduces to the original. Signed-off-by: Josef Bacik Co-Authored-By: Claude Opus 4.6 --- tools/lib/bpf/btf.c | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c index 83fe79ffcb8f..0b0bb5cba22b 100644 --- a/tools/lib/bpf/btf.c +++ b/tools/lib/bpf/btf.c @@ -2004,24 +2004,41 @@ int btf__add_btf(struct btf *btf, const struct btf *src_btf) { struct btf_pipe p = { .src = src_btf, .dst = btf }; int data_sz, sz, cnt, i, err, old_strs_len; + __u32 src_start_id; __u32 *off; void *t; - /* appending split BTF isn't supported yet */ - if (src_btf->base_btf) - return libbpf_err(-ENOTSUP); + /* When appending split BTF, the destination must share the same base + * BTF so that base type ID references remain valid. + */ + if (src_btf->base_btf && btf->base_btf && + src_btf->base_btf != btf->base_btf) + return libbpf_err(-EINVAL); + + src_start_id = src_btf->base_btf ? btf__type_cnt(src_btf->base_btf) : 1; /* deconstruct BTF, if necessary, and invalidate raw_data */ if (btf_ensure_modifiable(btf)) return libbpf_err(-ENOMEM); + /* If dst has a base BTF, ensure it is modifiable now. + * btf_rewrite_str() resolves strings via btf__str_by_offset(src), + * which for split src may return pointers into dst's base string + * data. btf__add_str(dst) then calls btf__find_str(dst->base), + * which triggers btf_ensure_modifiable(base) and may reallocate + * the base string data, invalidating the pointer. Pre-emptively + * making the base modifiable avoids this use-after-free. + */ + if (btf->base_btf && btf_ensure_modifiable(btf->base_btf)) + return libbpf_err(-ENOMEM); + /* remember original strings section size if we have to roll back * partial strings section changes */ old_strs_len = btf->hdr->str_len; data_sz = src_btf->hdr->type_len; - cnt = btf__type_cnt(src_btf) - 1; + cnt = src_btf->nr_types; /* pre-allocate enough memory for new types */ t = btf_add_type_mem(btf, data_sz); @@ -2074,11 +2091,16 @@ int btf__add_btf(struct btf *btf, const struct btf *src_btf) if (!*type_id) /* nothing to do for VOID references */ continue; - /* we haven't updated btf's type count yet, so - * btf->start_id + btf->nr_types - 1 is the type ID offset we should - * add to all newly added BTF types + /* For split BTF sources, type IDs referencing the + * base BTF (< src_start_id) remain unchanged since + * dst shares the same base. Only remap IDs in the + * split portion. For non-split sources src_start_id + * is 1 so all IDs are remapped as before. */ - *type_id += btf->start_id + btf->nr_types - 1; + if (*type_id < src_start_id) + continue; + + *type_id += btf->start_id + btf->nr_types - src_start_id; } /* go to next type data and type offset index entry */ -- 2.53.0