[PATCH v2 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader

From: Siddharth Nayyar

Date: Tue May 26 2026 - 08:13:27 EST


BPF `STRUCT_OPS` maps (such as `sched_ext_ops` maps) require resolving
and plumbing the kernel-side structure value type ID
(`btf_vmlinux_value_type_id`) into the BPF map creation system call
attributes. Additionally, when `btf_vmlinux_value_type_id` is supplied,
the kernel requires a valid userspace BTF file descriptor (`btf_fd`) to
be supplied to verify types.

Previously, the `gen_loader` map creation generator
(`bpf_gen__map_create()`) completely omitted plumbing
`btf_vmlinux_value_type_id` because `attr_size` was calculated only up
to `map_extra` (which resides before `btf_vmlinux_value_type_id` in
`union bpf_attr`).

Furthermore, `gen_loader.c` only copied the loaded `btf_fd` from the
stack to the attributes blob if `btf_value_type_id` was non-zero.
Because `STRUCT_OPS` maps explicitly zero out `btf_value_type_id`, the
loader program skipped copying `btf_fd`, leaving it as `0` (standard
input), which caused the kernel's `btf_get_by_fd(0)` check to fail.

Fix this by:

1. Expanding `attr_size` inside `bpf_gen__map_create()` to include
`btf_vmlinux_value_type_id` and copying it from the options.
2. Modifying the `btf_fd` copying condition to populate `btf_fd` if
either `btf_value_type_id` OR `btf_vmlinux_value_type_id` is set.

Signed-off-by: Siddharth Nayyar <sidnayyar@xxxxxxxxxx>
---
tools/lib/bpf/gen_loader.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
index cd5c2543f54d..27b0353d1082 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -507,7 +507,7 @@ void bpf_gen__map_create(struct bpf_gen *gen,
__u32 key_size, __u32 value_size, __u32 max_entries,
struct bpf_map_create_opts *map_attr, int map_idx)
{
- int attr_size = offsetofend(union bpf_attr, map_extra);
+ int attr_size = offsetofend(union bpf_attr, btf_vmlinux_value_type_id);
bool close_inner_map_fd = false;
int map_create_attr, idx;
union bpf_attr attr;
@@ -525,13 +525,14 @@ void bpf_gen__map_create(struct bpf_gen *gen,
attr.max_entries = tgt_endian(max_entries);
attr.btf_key_type_id = tgt_endian(map_attr->btf_key_type_id);
attr.btf_value_type_id = tgt_endian(map_attr->btf_value_type_id);
+ attr.btf_vmlinux_value_type_id = tgt_endian(map_attr->btf_vmlinux_value_type_id);

map_create_attr = add_data(gen, &attr, attr_size);
pr_debug("gen: map_create: %s idx %d type %d value_type_id %d, attr: off %d size %d\n",
map_name, map_idx, map_type, map_attr->btf_value_type_id,
map_create_attr, attr_size);

- if (map_attr->btf_value_type_id)
+ if (map_attr->btf_value_type_id || map_attr->btf_vmlinux_value_type_id)
/* populate union bpf_attr with btf_fd saved in the stack earlier */
move_stack2blob(gen, attr_field(map_create_attr, btf_fd), 4,
stack_off(btf_fd));

--
2.54.0.746.g67dd491aae-goog