[PATCH v1 bpf-next 03/18] bpf: Build fmod_ret target sets from BTF decl tags

From: Anton Protopopov

Date: Fri Nov 06 2026 - 15:57:08 EST


Mark functions tagged with the "bpf_fmodret{,_sleepable}" decl tags
(added by resolve_btfids) as fmodret targets. Add the KF_SLEEPABLE flag
for the latter.

Do this for vmlinux at fs_initcall and for a module before its BTF
becomes visible to anyone else, so nothing can look at the set while it
is built.

The IDs come from the BTF after relocation, so they are final. Unlike
the IDs of a set registered by a module, they must not be relocated, and
the set is installed directly instead of through btf_populate_kfunc_set().
(The register_btf_fmodret_id_set() keeps working, it will be removed by
a later patch.)

The set shares kfunc_set_tab with the kfunc sets, but its flags only say
how a target may be attached. Skip it in btf_kfunc_check_flag(), so a
kfunc which is also an fmod_ret target keeps its own flags, such as
KF_IMPLICIT_ARGS. Otherwise btf_attach_func_proto() finds the flags of
such a kfunc inconsistent and attaching to it fails.

Signed-off-by: Anton Protopopov <a.s.protopopov@xxxxxxxxx>
---
kernel/bpf/btf.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 113 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 61d2df6776a9..2c45e5fa1d7b 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -9307,6 +9307,8 @@ enum {
};

#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
+static int btf_register_fmodret_tags(struct btf *btf);
+
struct btf_module {
struct list_head list;
struct module *module;
@@ -9476,6 +9478,15 @@ static int btf_module_notify(struct notifier_block *nb, unsigned long op,
}
goto out;
}
+ /*
+ * Do this before the BTF is visible to anyone else. A failure
+ * is not fatal: the module loads, but without fmod_ret targets.
+ */
+ err = btf_register_fmodret_tags(btf);
+ if (err)
+ pr_warn("failed to register fmod_ret targets of module [%s]: %d\n",
+ mod->name, err);
+
err = btf_alloc_id(btf);
if (err) {
btf_free(btf);
@@ -10100,7 +10111,9 @@ u32 *btf_kfunc_flags(const struct btf *btf, u32 kfunc_btf_id, const struct bpf_p
}

/*
- * Check a single KF_* @flag on a kfunc across all of its hook sets.
+ * Check a single KF_* @flag on a kfunc across all of its hook sets. The set of
+ * fmod_ret targets is not one of them: its flags only say how a target may be
+ * attached, and a kfunc which is also a target keeps its own flags.
* Returns:
* * 1 if @flag is set
* * 0 if @flag is not set
@@ -10115,6 +10128,8 @@ int btf_kfunc_check_flag(const struct btf *btf, u32 kfunc_btf_id, u32 flag)
u32 *flags;

for (hook = 0; hook < BTF_KFUNC_HOOK_MAX; hook++) {
+ if (hook == BTF_KFUNC_HOOK_FMODRET)
+ continue;
flags = btf_kfunc_id_set_contains(btf, hook, kfunc_btf_id);
if (!flags)
continue;
@@ -10182,6 +10197,103 @@ int register_btf_kfunc_id_set(enum bpf_prog_type prog_type,
}
EXPORT_SYMBOL_GPL(register_btf_kfunc_id_set);

+/*
+ * Targets of fmod_ret programs are marked in BTF by resolve_btfids, see
+ * BTF_SET8_FMODRET. A "bpf_fmodret" decl tag on a function makes it a target.
+ * A target which sleepable programs may attach to also has a
+ * "bpf_fmodret_sleepable" tag, which never comes without "bpf_fmodret".
+ */
+#define BTF_FMODRET_TAG "bpf_fmodret"
+#define BTF_FMODRET_SLEEPABLE_TAG "bpf_fmodret_sleepable"
+
+/* Return the function which T tags with decl tag NAME, or 0 */
+static u32 btf_fmodret_tag_func(const struct btf *btf, const struct btf_type *t,
+ const char *name)
+{
+ const struct btf_type *func;
+
+ if (!btf_type_is_decl_tag(t) || btf_type_decl_tag(t)->component_idx != -1)
+ return 0;
+ if (strcmp(btf_name_by_offset(btf, t->name_off), name))
+ return 0;
+ func = btf_type_by_id(btf, t->type);
+ if (!func || !btf_type_is_func(func))
+ return 0;
+ return t->type;
+}
+
+/* Build the set of fmod_ret targets of BTF from its decl tags */
+static int btf_register_fmodret_tags(struct btf *btf)
+{
+ u32 id, func_id, nr_types = btf_nr_types(btf), cnt = 0;
+ struct btf_kfunc_set_tab *tab;
+ struct btf_id_set8 *set;
+
+ for (id = btf_start_id(btf); id < nr_types; id++)
+ cnt += !!btf_fmodret_tag_func(btf, btf_type_by_id(btf, id), BTF_FMODRET_TAG);
+ if (!cnt)
+ return 0;
+
+ set = kmalloc(struct_size(set, pairs, cnt), GFP_KERNEL | __GFP_NOWARN);
+ if (!set)
+ return -ENOMEM;
+ set->flags = 0;
+ set->cnt = 0;
+ for (id = btf_start_id(btf); id < nr_types; id++) {
+ func_id = btf_fmodret_tag_func(btf, btf_type_by_id(btf, id), BTF_FMODRET_TAG);
+ if (!func_id)
+ continue;
+ set->pairs[set->cnt].id = func_id;
+ set->pairs[set->cnt].flags = 0;
+ set->cnt++;
+ }
+ sort(set->pairs, set->cnt, sizeof(set->pairs[0]), btf_id_cmp_func, NULL);
+
+ /* Each sleepable tag marks a function which is already in the set */
+ for (id = btf_start_id(btf); id < nr_types; id++) {
+ struct btf_id_set8_pair *pair;
+
+ func_id = btf_fmodret_tag_func(btf, btf_type_by_id(btf, id),
+ BTF_FMODRET_SLEEPABLE_TAG);
+ if (!func_id)
+ continue;
+ pair = btf_id_set8_entry(set, func_id);
+ if (pair)
+ pair->flags |= KF_SLEEPABLE;
+ }
+
+ tab = btf->kfunc_set_tab;
+ if (!tab) {
+ tab = kzalloc_obj(*tab, GFP_KERNEL | __GFP_NOWARN);
+ if (!tab) {
+ kfree(set);
+ return -ENOMEM;
+ }
+ btf->kfunc_set_tab = tab;
+ }
+ if (tab->sets[BTF_KFUNC_HOOK_FMODRET]) {
+ kfree(set);
+ return -EEXIST;
+ }
+ tab->sets[BTF_KFUNC_HOOK_FMODRET] = set;
+ return 0;
+}
+
+static int __init btf_fmodret_tags_init(void)
+{
+ struct btf *btf;
+ int err;
+
+ btf = btf_get_module_btf(NULL);
+ if (IS_ERR_OR_NULL(btf))
+ return PTR_ERR_OR_ZERO(btf);
+
+ err = btf_register_fmodret_tags(btf);
+ btf_put(btf);
+ return err;
+}
+fs_initcall(btf_fmodret_tags_init);
+
/* This function must be invoked only from initcalls/module init functions */
int register_btf_fmodret_id_set(const struct btf_kfunc_id_set *kset)
{
--
2.43.0