From 47d62db5504300bb0f85983e45726905be7166cb Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:11 -0700 Subject: [PATCH 1/8] resolve_btfids: Implement generic ensure_mem() to grow arrays push_*() helpers in resolve_btfids contain copy-pasted array growth logic. Factor it out into ensure_mem() - a simplified variant of libbpf_ensure_mem(), and use it in the helpers. Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-2-ihor.solodrai@linux.dev --- tools/bpf/resolve_btfids/main.c | 55 ++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c index f8a91fa7584f..183466ddc5f9 100644 --- a/tools/bpf/resolve_btfids/main.c +++ b/tools/bpf/resolve_btfids/main.c @@ -201,6 +201,35 @@ static int eprintf(int level, int var, const char *fmt, ...) #define pr_info(fmt, ...) \ eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) +/* + * Grow *data so it can hold at least cnt elements of elem_sz bytes each. + * *cap is the capacity in elements and is updated on growth. + */ +static int __ensure_mem(void **data, u32 *cap, u32 cnt, size_t elem_sz) +{ + u32 new_cap, old_cap = *cap; + void *arr; + + if (cnt <= old_cap) + return 0; + + new_cap = max(old_cap + 256, old_cap * 2); + if (new_cap < cnt) + new_cap = cnt; + + arr = realloc(*data, elem_sz * new_cap); + if (!arr) + return -ENOMEM; + + *data = arr; + *cap = new_cap; + + return 0; +} + +#define ensure_mem(arr_ptr, cap_ptr, cnt) \ + __ensure_mem((void **)(arr_ptr), (cap_ptr), (cnt), sizeof(**(arr_ptr))) + static bool is_btf_id(const char *name) { return name && !strncmp(name, BTF_ID_PREFIX, sizeof(BTF_ID_PREFIX) - 1); @@ -890,17 +919,8 @@ static const struct btf_type *btf_type_skip_qualifiers(const struct btf *btf, s3 static int push_decl_tag_id(struct btf2btf_context *ctx, u32 decl_tag_id) { - u32 *arr = ctx->decl_tags; - u32 cap = ctx->max_decl_tags; - - if (ctx->nr_decl_tags + 1 > cap) { - cap = max(cap + 256, cap * 2); - arr = realloc(arr, sizeof(u32) * cap); - if (!arr) - return -ENOMEM; - ctx->max_decl_tags = cap; - ctx->decl_tags = arr; - } + if (ensure_mem(&ctx->decl_tags, &ctx->max_decl_tags, ctx->nr_decl_tags + 1)) + return -ENOMEM; ctx->decl_tags[ctx->nr_decl_tags++] = decl_tag_id; @@ -909,17 +929,8 @@ static int push_decl_tag_id(struct btf2btf_context *ctx, u32 decl_tag_id) static int push_kfunc(struct btf2btf_context *ctx, struct kfunc *kfunc) { - struct kfunc *arr = ctx->kfuncs; - u32 cap = ctx->max_kfuncs; - - if (ctx->nr_kfuncs + 1 > cap) { - cap = max(cap + 256, cap * 2); - arr = realloc(arr, sizeof(struct kfunc) * cap); - if (!arr) - return -ENOMEM; - ctx->max_kfuncs = cap; - ctx->kfuncs = arr; - } + if (ensure_mem(&ctx->kfuncs, &ctx->max_kfuncs, ctx->nr_kfuncs + 1)) + return -ENOMEM; ctx->kfuncs[ctx->nr_kfuncs++] = *kfunc; From c5ab68a5fa9212c06f56d2ace6aa2c9249af33c4 Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:12 -0700 Subject: [PATCH 2/8] resolve_btfids: Index BTF ID symbols by address Keep an address-sorted index of parsed .BTF_ids symbols so that the original BTF_ID symbol name can be recovered from an entry address. Use the index in find_kfunc_flags() to scan BTF_SET8_KFUNCS entries directly and match each entry back to the requested kfunc. Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Emil Tsalapatis Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-3-ihor.solodrai@linux.dev --- tools/bpf/resolve_btfids/main.c | 94 +++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c index 183466ddc5f9..3198198b03a7 100644 --- a/tools/bpf/resolve_btfids/main.c +++ b/tools/bpf/resolve_btfids/main.c @@ -119,6 +119,11 @@ struct btf_id { Elf64_Addr addr[ADDR_CNT]; }; +struct addr_sym { + Elf64_Addr addr; + const char *name; +}; + struct object { const char *path; const char *btf_path; @@ -150,6 +155,10 @@ struct object { int nr_structs; int nr_unions; int nr_typedefs; + + struct addr_sym *addr_syms; + u32 addr_syms_cnt; + u32 addr_syms_cap; }; #define KF_IMPLICIT_ARGS (1 << 16) @@ -509,6 +518,40 @@ static int elf_collect(struct object *obj) return 0; } +static int push_addr_sym(struct object *obj, Elf64_Addr addr, const char *name) +{ + if (ensure_mem(&obj->addr_syms, &obj->addr_syms_cap, obj->addr_syms_cnt + 1)) + return -ENOMEM; + + obj->addr_syms[obj->addr_syms_cnt++] = (struct addr_sym){ + .addr = addr, + .name = name, + }; + + return 0; +} + +static int cmp_addr_sym(const void *a, const void *b) +{ + Elf64_Addr aa = ((const struct addr_sym *)a)->addr; + Elf64_Addr ab = ((const struct addr_sym *)b)->addr; + + return (aa > ab) - (aa < ab); +} + +static const char *find_name_by_addr(struct object *obj, Elf64_Addr addr) +{ + struct addr_sym key = { .addr = addr }; + struct addr_sym *res; + + if (!obj->addr_syms_cnt) + return NULL; + + res = bsearch(&key, obj->addr_syms, obj->addr_syms_cnt, + sizeof(*obj->addr_syms), cmp_addr_sym); + return res ? res->name : NULL; +} + static int symbols_collect(struct object *obj) { Elf_Scn *scn = NULL; @@ -602,8 +645,15 @@ static int symbols_collect(struct object *obj) return -1; } id->addr[id->addr_cnt++] = sym.st_value; + + if (push_addr_sym(obj, sym.st_value, id->name)) + return -1; } + if (obj->addr_syms_cnt) + qsort(obj->addr_syms, obj->addr_syms_cnt, + sizeof(*obj->addr_syms), cmp_addr_sym); + return 0; } @@ -957,43 +1007,40 @@ static int collect_decl_tags(struct btf2btf_context *ctx) } /* - * To find the kfunc flags having its struct btf_id (with ELF addresses) - * we need to find the address that is in range of a set8. - * If a set8 is found, then the flags are located at addr + 4 bytes. + * To find kfunc flags, scan BTF_SET8_KFUNCS entries and use the entry + * address to recover the corresponding BTF_ID symbol name. * Return 0 (no flags!) if not found. */ static u32 find_kfunc_flags(struct object *obj, struct btf_id *kfunc_id) { - const u32 *elf_data_ptr = obj->efile.idlist->d_buf; - u64 set_lower_addr, set_upper_addr, addr; + Elf_Data *idlist = obj->efile.idlist; struct btf_id *set_id; struct rb_node *next; - u32 flags; - u64 idx; for (next = rb_first(&obj->sets); next; next = rb_next(next)) { + struct btf_id_set8 *set8; + u64 set_addr; + set_id = rb_entry(next, struct btf_id, rb_node); if (set_id->kind != BTF_ID_KIND_SET8 || set_id->addr_cnt != 1) continue; - set_lower_addr = set_id->addr[0]; - set_upper_addr = set_lower_addr + set_id->cnt * sizeof(u64); + set_addr = set_id->addr[0]; + set8 = idlist->d_buf + (set_addr - obj->efile.idlist_addr); + if (!(set8->flags & BTF_SET8_KFUNCS)) + continue; - for (u32 i = 0; i < kfunc_id->addr_cnt; i++) { - addr = kfunc_id->addr[i]; - /* - * Lower bound is exclusive to skip the 8-byte header of the set. - * Upper bound is inclusive to capture the last entry at offset 8*cnt. - */ - if (set_lower_addr < addr && addr <= set_upper_addr) { - pr_debug("found kfunc %s in BTF_ID_FLAGS %s\n", - kfunc_id->name, set_id->name); - idx = addr - obj->efile.idlist_addr; - idx = idx / sizeof(u32) + 1; - flags = elf_data_ptr[idx]; + for (u32 i = 0; i < set_id->cnt; i++) { + size_t off = (char *)&set8->pairs[i] - (char *)set8; + const char *name = find_name_by_addr(obj, set_addr + off); - return flags; - } + if (!name || strcmp(name, kfunc_id->name) != 0) + continue; + + pr_debug("found kfunc %s in BTF_ID_FLAGS %s\n", + kfunc_id->name, set_id->name); + + return set8->pairs[i].flags; } } @@ -1586,6 +1633,7 @@ out: btf_id__free_all(&obj.typedefs); btf_id__free_all(&obj.funcs); btf_id__free_all(&obj.sets); + free(obj.addr_syms); if (obj.efile.elf) { elf_end(obj.efile.elf); close(obj.efile.fd); From e4293c31a32061b66bb7257ab2538cebfc61c84a Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:13 -0700 Subject: [PATCH 3/8] resolve_btfids: Keep collected kfuncs in a rbtree Store collected kfuncs in a rbtree keyed by BTF ID instead of a dynamically grown array. This allows for efficient deduplication for kfuncs declared in multiple sets, which is needed for subsequent patches [1]. [1] https://lore.kernel.org/bpf/CAEf4BzaLzX3mXvQzxv+gbmZOh84XvYofLjMSWFYghNjS-ohEZg@mail.gmail.com/ Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-4-ihor.solodrai@linux.dev --- tools/bpf/resolve_btfids/main.c | 50 +++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c index 3198198b03a7..de4986e1bc3d 100644 --- a/tools/bpf/resolve_btfids/main.c +++ b/tools/bpf/resolve_btfids/main.c @@ -165,6 +165,7 @@ struct object { #define KF_IMPL_SUFFIX "_impl" struct kfunc { + struct rb_node rb_node; const char *name; u32 btf_id; u32 flags; @@ -175,9 +176,7 @@ struct btf2btf_context { u32 *decl_tags; u32 nr_decl_tags; u32 max_decl_tags; - struct kfunc *kfuncs; - u32 nr_kfuncs; - u32 max_kfuncs; + struct rb_root kfuncs; }; static int verbose; @@ -979,14 +978,48 @@ static int push_decl_tag_id(struct btf2btf_context *ctx, u32 decl_tag_id) static int push_kfunc(struct btf2btf_context *ctx, struct kfunc *kfunc) { - if (ensure_mem(&ctx->kfuncs, &ctx->max_kfuncs, ctx->nr_kfuncs + 1)) + struct rb_node **p = &ctx->kfuncs.rb_node; + struct rb_node *parent = NULL; + struct kfunc *k; + + /* Dedup by BTF ID: collecting the same kfunc twice is a no-op. */ + while (*p) { + parent = *p; + k = rb_entry(parent, struct kfunc, rb_node); + + if (kfunc->btf_id < k->btf_id) + p = &(*p)->rb_left; + else if (kfunc->btf_id > k->btf_id) + p = &(*p)->rb_right; + else + return 0; + } + + k = zalloc(sizeof(*k)); + if (!k) return -ENOMEM; - ctx->kfuncs[ctx->nr_kfuncs++] = *kfunc; + *k = *kfunc; + rb_link_node(&k->rb_node, parent, p); + rb_insert_color(&k->rb_node, &ctx->kfuncs); return 0; } +static void free_kfuncs(struct rb_root *root) +{ + struct rb_node *next; + struct kfunc *kfunc; + + next = rb_first(root); + while (next) { + kfunc = rb_entry(next, struct kfunc, rb_node); + next = rb_next(&kfunc->rb_node); + rb_erase(&kfunc->rb_node, root); + free(kfunc); + } +} + static int collect_decl_tags(struct btf2btf_context *ctx) { const u32 type_cnt = btf__type_cnt(ctx->btf); @@ -1272,14 +1305,15 @@ add_new_proto: static int btf2btf(struct object *obj) { struct btf2btf_context ctx = {}; + struct rb_node *next; int err; err = build_btf2btf_context(obj, &ctx); if (err) goto out; - for (u32 i = 0; i < ctx.nr_kfuncs; i++) { - struct kfunc *kfunc = &ctx.kfuncs[i]; + for (next = rb_first(&ctx.kfuncs); next; next = rb_next(next)) { + struct kfunc *kfunc = rb_entry(next, struct kfunc, rb_node); if (!(kfunc->flags & KF_IMPLICIT_ARGS)) continue; @@ -1292,7 +1326,7 @@ static int btf2btf(struct object *obj) err = 0; out: free(ctx.decl_tags); - free(ctx.kfuncs); + free_kfuncs(&ctx.kfuncs); return err; } From ef242559eb483466a959020af2e404bda77b0015 Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:14 -0700 Subject: [PATCH 4/8] libbpf: Export btf__find_by_name_kind_own() btf__find_by_name_kind() searches the base BTF before the split BTF, so in case of a name collision between base and split it always returns a base type. Tools that process split BTF may need to restrict a lookup to the split's own types. The internal helper btf__find_by_name_kind_own() already does exactly that. Make it a public API. Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-5-ihor.solodrai@linux.dev --- tools/lib/bpf/btf.h | 2 ++ tools/lib/bpf/libbpf.map | 1 + tools/lib/bpf/libbpf_internal.h | 2 -- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h index 1a31f2da947f..587172c0de08 100644 --- a/tools/lib/bpf/btf.h +++ b/tools/lib/bpf/btf.h @@ -172,6 +172,8 @@ LIBBPF_API __s32 btf__find_by_name(const struct btf *btf, const char *type_name); LIBBPF_API __s32 btf__find_by_name_kind(const struct btf *btf, const char *type_name, __u32 kind); +LIBBPF_API __s32 btf__find_by_name_kind_own(const struct btf *btf, + const char *type_name, __u32 kind); LIBBPF_API __u32 btf__type_cnt(const struct btf *btf); LIBBPF_API const struct btf *btf__base_btf(const struct btf *btf); LIBBPF_API const struct btf_type *btf__type_by_id(const struct btf *btf, diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index b731df19ae69..08ab2ea881fb 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -460,5 +460,6 @@ LIBBPF_1.8.0 { global: bpf_program__attach_tracing_multi; bpf_program__clone; + btf__find_by_name_kind_own; btf__new_empty_opts; } LIBBPF_1.7.0; diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h index d5b7db703b3f..7a74abb904f8 100644 --- a/tools/lib/bpf/libbpf_internal.h +++ b/tools/lib/bpf/libbpf_internal.h @@ -596,8 +596,6 @@ typedef int (*type_id_visit_fn)(__u32 *type_id, void *ctx); typedef int (*str_off_visit_fn)(__u32 *str_off, void *ctx); int btf_ext_visit_type_ids(struct btf_ext *btf_ext, type_id_visit_fn visit, void *ctx); int btf_ext_visit_str_offs(struct btf_ext *btf_ext, str_off_visit_fn visit, void *ctx); -__s32 btf__find_by_name_kind_own(const struct btf *btf, const char *type_name, - __u32 kind); /* handle direct returned errors */ static inline int libbpf_err(int ret) From 91efe50e3042ffe08eb7f0d77938cf237992241e Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:15 -0700 Subject: [PATCH 5/8] resolve_btfids: Fix the _impl lookup for module BTF process_kfunc_with_implicit_args() skips generating _impl function when one already exists for backwards compatibility. It uses btf__find_by_name_kind(), which searches the base BTF before the split BTF. When resolve_btfids processes a module, a same-named _impl in vmlinux would be found and the module's own counterpart would not be created. Fix by using btf__find_by_name_kind_own() for the lookup. Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-6-ihor.solodrai@linux.dev --- tools/bpf/resolve_btfids/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c index de4986e1bc3d..ab3ab3045592 100644 --- a/tools/bpf/resolve_btfids/main.c +++ b/tools/bpf/resolve_btfids/main.c @@ -1232,7 +1232,7 @@ static int process_kfunc_with_implicit_args(struct btf2btf_context *ctx, struct return -E2BIG; } - if (btf__find_by_name_kind(btf, tmp_name, BTF_KIND_FUNC) > 0) { + if (btf__find_by_name_kind_own(btf, tmp_name, BTF_KIND_FUNC) > 0) { pr_debug("resolve_btfids: function %s already exists in BTF\n", tmp_name); goto add_new_proto; } From 5c4923172dae6f4cc97567cf86a08e6f250a641d Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:16 -0700 Subject: [PATCH 6/8] HID: bpf: Make syscall kfunc flags match the struct_ops set Update kfunc flags for hid_bpf_syscall_kfunc_ids set to exactly match hid_bpf_kfunc_ids set by adding KF_SLEEPABLE flag. The syscall set omitted the flag because syscall programs are always sleepable (the verifier rejects a non-sleepable syscall program). However the upcoming resolve_btfids change enforces per-kfunc flag consistency across BTF ID sets at build time, which is why this change is necessary. Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-7-ihor.solodrai@linux.dev --- drivers/hid/bpf/hid_bpf_dispatch.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 536f6d01fd14..44671dbdeca8 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -590,11 +590,11 @@ static const struct btf_kfunc_id_set hid_bpf_kfunc_set = { /* for syscall HID-BPF */ BTF_KFUNCS_START(hid_bpf_syscall_kfunc_ids) -BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL) -BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE) -BTF_ID_FLAGS(func, hid_bpf_hw_request) -BTF_ID_FLAGS(func, hid_bpf_hw_output_report) -BTF_ID_FLAGS(func, hid_bpf_input_report) +BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE) +BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE) BTF_KFUNCS_END(hid_bpf_syscall_kfunc_ids) static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = { From f9f60d41ba2c84bf74e42a3a09744561e770adc8 Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:17 -0700 Subject: [PATCH 7/8] resolve_btfids: Discover kfuncs from BTF ID sets collect_kfuncs() currently uses bpf_kfunc decl tags to identify the list of kfuncs. The decl tags are generated by pahole, which makes current implementation implicitly rely on those tags being generated. The authoritative source, used by the the BPF verifier for kfunc registration, of functions being BPF kfuncs are BTF_KFUNCS_START()/END() declarations. These are BTF_ID_SET8 under the hood. Currently resolve_btfids reads kfunc flags from these sets, and populates them with BTF IDs. Implement kfunc discovery from BTF_ID_SET8 symbols in resolve_btfids, removing the dependency on pahole's emmission of decl tags. Walk BTF_ID_KIND_SET8 sets, and use the address-to-symbol index to look up set entry's BTF_ID symbol name (before .BTF_ids is patched), recording the paired flags directly. This makes find_kfunc_flags() helper unnecessary, so it's removed. Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-8-ihor.solodrai@linux.dev --- tools/bpf/resolve_btfids/main.c | 89 +++++++++++---------------------- 1 file changed, 29 insertions(+), 60 deletions(-) diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c index ab3ab3045592..338d0c0a8e58 100644 --- a/tools/bpf/resolve_btfids/main.c +++ b/tools/bpf/resolve_btfids/main.c @@ -1039,19 +1039,18 @@ static int collect_decl_tags(struct btf2btf_context *ctx) return 0; } -/* - * To find kfunc flags, scan BTF_SET8_KFUNCS entries and use the entry - * address to recover the corresponding BTF_ID symbol name. - * Return 0 (no flags!) if not found. - */ -static u32 find_kfunc_flags(struct object *obj, struct btf_id *kfunc_id) +static int collect_kfuncs(struct object *obj, struct btf2btf_context *ctx) { Elf_Data *idlist = obj->efile.idlist; - struct btf_id *set_id; + struct btf *btf = ctx->btf; struct rb_node *next; + if (!idlist || !idlist->d_buf) + return 0; + for (next = rb_first(&obj->sets); next; next = rb_next(next)) { struct btf_id_set8 *set8; + struct btf_id *set_id; u64 set_addr; set_id = rb_entry(next, struct btf_id, rb_node); @@ -1066,69 +1065,39 @@ static u32 find_kfunc_flags(struct object *obj, struct btf_id *kfunc_id) for (u32 i = 0; i < set_id->cnt; i++) { size_t off = (char *)&set8->pairs[i] - (char *)set8; const char *name = find_name_by_addr(obj, set_addr + off); + struct kfunc kfunc; + s32 func_id; + int err; - if (!name || strcmp(name, kfunc_id->name) != 0) + if (!name) { + pr_err("WARN: resolve_btfids: no BTF ID symbol for %s entry %u\n", + set_id->name, i); + warnings++; continue; + } - pr_debug("found kfunc %s in BTF_ID_FLAGS %s\n", - kfunc_id->name, set_id->name); + func_id = btf__find_by_name_kind_own(btf, name, BTF_KIND_FUNC); + if (func_id < 0) { + pr_err("WARN: resolve_btfids: no BTF func for kfunc %s in %s\n", + name, set_id->name); + warnings++; + continue; + } - return set8->pairs[i].flags; + pr_debug("found kfunc %s in %s\n", name, set_id->name); + + kfunc.name = name; + kfunc.btf_id = func_id; + kfunc.flags = set8->pairs[i].flags; + err = push_kfunc(ctx, &kfunc); + if (err) + return err; } } return 0; } -static int collect_kfuncs(struct object *obj, struct btf2btf_context *ctx) -{ - const char *tag_name, *func_name; - struct btf *btf = ctx->btf; - const struct btf_type *t; - u32 flags, func_id; - struct kfunc kfunc; - struct btf_id *id; - int err; - - if (ctx->nr_decl_tags == 0) - return 0; - - for (u32 i = 0; i < ctx->nr_decl_tags; i++) { - t = btf__type_by_id(btf, ctx->decl_tags[i]); - if (btf_kflag(t) || btf_decl_tag(t)->component_idx != -1) - continue; - - tag_name = btf__name_by_offset(btf, t->name_off); - if (strcmp(tag_name, "bpf_kfunc") != 0) - continue; - - func_id = t->type; - t = btf__type_by_id(btf, func_id); - if (!btf_is_func(t)) - continue; - - func_name = btf__name_by_offset(btf, t->name_off); - if (!func_name) - continue; - - id = btf_id__find(&obj->funcs, func_name); - if (!id || id->kind != BTF_ID_KIND_SYM) - continue; - - flags = find_kfunc_flags(obj, id); - - kfunc.name = id->name; - kfunc.btf_id = func_id; - kfunc.flags = flags; - - err = push_kfunc(ctx, &kfunc); - if (err) - return err; - } - - return 0; -} - static int build_btf2btf_context(struct object *obj, struct btf2btf_context *ctx) { int err; From 140a3479ef66507a7de06f4cd8bcefb86d2c640a Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 22 Jul 2026 16:35:18 -0700 Subject: [PATCH 8/8] resolve_btfids: Enforce consistent kfunc flags across BTF ID sets A kfunc may be listed in several BTF ID sets, which is expected because different kfuncs are available to BPF programs depending on their type. However kfunc flags across different BTF ID sets must be consistent [1]. The flags should be considered a part of the kfunc declaration, because they influence its BTF representation and verifier handling. Enforce the kfunc flag consistency in resolve_btifds by hard failing on error and blocking kernel (or module) build. [1] https://lore.kernel.org/bpf/9b2196dd-443b-4632-ae11-030cdbdc59b4@linux.dev/ Signed-off-by: Ihor Solodrai Signed-off-by: Andrii Nakryiko Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260722233518.778854-9-ihor.solodrai@linux.dev --- tools/bpf/resolve_btfids/main.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/bpf/resolve_btfids/main.c b/tools/bpf/resolve_btfids/main.c index 338d0c0a8e58..85488935909d 100644 --- a/tools/bpf/resolve_btfids/main.c +++ b/tools/bpf/resolve_btfids/main.c @@ -982,17 +982,26 @@ static int push_kfunc(struct btf2btf_context *ctx, struct kfunc *kfunc) struct rb_node *parent = NULL; struct kfunc *k; - /* Dedup by BTF ID: collecting the same kfunc twice is a no-op. */ + /* + * Dedup by BTF ID: collecting the same kfunc twice is a no-op, + * UNLESS the kfunc flags are inconsistent, in which case we + * fail hard because it indicates a bug in a kfunc set declaration. + */ while (*p) { parent = *p; k = rb_entry(parent, struct kfunc, rb_node); - if (kfunc->btf_id < k->btf_id) + if (kfunc->btf_id < k->btf_id) { p = &(*p)->rb_left; - else if (kfunc->btf_id > k->btf_id) + } else if (kfunc->btf_id > k->btf_id) { p = &(*p)->rb_right; - else + } else if (k->flags == kfunc->flags) { return 0; + } else { + pr_err("ERROR: resolve_btfids: kfunc %s has inconsistent flags across BTF ID sets: 0x%x != 0x%x\n", + kfunc->name, k->flags, kfunc->flags); + return -EINVAL; + } } k = zalloc(sizeof(*k));