* [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops
2026-05-27 12:52 [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons Siddharth Nayyar
@ 2026-05-27 12:52 ` Siddharth Nayyar
2026-05-27 13:25 ` sashiko-bot
2026-05-27 13:39 ` bot+bpf-ci
2026-05-27 12:52 ` [PATCH v4 2/3] libbpf: zero out btf_key_type_id for STRUCT_OPS maps Siddharth Nayyar
` (2 subsequent siblings)
3 siblings, 2 replies; 13+ messages in thread
From: Siddharth Nayyar @ 2026-05-27 12:52 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov,
Daniel Borkmann, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa
Cc: bpf, linux-kernel, gprocida, maennich, Siddharth Nayyar
During light skeleton generation (`bpftool gen skeleton -L`), libbpf
runs in gen_loader mode. Previously, `bpf_object__load_vmlinux_btf()`
completely bypassed loading the kernel vmlinux BTF (`obj->btf_vmlinux`)
if `gen_loader` was active.
However, BPF `struct_ops` maps (such as `sched_ext_ops` maps) require
resolving the kernel-side struct type IDs and member sizes at
compile/skeleton generation time. Without loading `btf_vmlinux`, libbpf
cannot query the kernel BTF types, causing light skeleton generation for
`struct_ops` to fail or omit crucial type information.
Fix this by modifying the check to load `btf_vmlinux` even in
`gen_loader` mode if the BPF object contains `struct_ops` maps.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
---
tools/lib/bpf/libbpf.c | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 3a80a018fc7d..b159faae7f9c 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -3544,15 +3544,20 @@ static bool prog_needs_vmlinux_btf(struct bpf_program *prog)
return false;
}
-static bool map_needs_vmlinux_btf(struct bpf_map *map)
+static bool obj_maps_need_vmlinux_btf(const struct bpf_object *obj)
{
- return bpf_map__is_struct_ops(map);
+ struct bpf_map *map;
+
+ bpf_object__for_each_map(map, obj) {
+ if (bpf_map__is_struct_ops(map))
+ return true;
+ }
+ return false;
}
static bool obj_needs_vmlinux_btf(const struct bpf_object *obj)
{
struct bpf_program *prog;
- struct bpf_map *map;
int i;
/* CO-RE relocations need kernel BTF, only when btf_custom_path
@@ -3577,12 +3582,7 @@ static bool obj_needs_vmlinux_btf(const struct bpf_object *obj)
return true;
}
- bpf_object__for_each_map(map, obj) {
- if (map_needs_vmlinux_btf(map))
- return true;
- }
-
- return false;
+ return obj_maps_need_vmlinux_btf(obj);
}
static int bpf_object__load_vmlinux_btf(struct bpf_object *obj, bool force)
@@ -3590,7 +3590,11 @@ static int bpf_object__load_vmlinux_btf(struct bpf_object *obj, bool force)
int err;
/* btf_vmlinux could be loaded earlier */
- if (obj->btf_vmlinux || obj->gen_loader)
+ if (obj->btf_vmlinux)
+ return 0;
+
+ /* only struct_ops maps need btf_vmlinux in gen_loader */
+ if (obj->gen_loader && !obj_maps_need_vmlinux_btf(obj))
return 0;
if (!force && !obj_needs_vmlinux_btf(obj))
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops
2026-05-27 12:52 ` [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops Siddharth Nayyar
@ 2026-05-27 13:25 ` sashiko-bot
2026-05-27 13:39 ` bot+bpf-ci
1 sibling, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-05-27 13:25 UTC (permalink / raw)
To: Siddharth Nayyar; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Enabling light skeleton generation for `struct_ops` maps exposes incomplete and fundamentally broken loader logic, which will always fail on the target system.
--
commit 241c33de1404c9ec6cd0b388a0a3bc61171e6646
Author: Siddharth Nayyar <sidnayyar@google.com>
libbpf: load vmlinux BTF in gen_loader mode for struct_ops
This patch modifies the BTF loading logic in libbpf to ensure vmlinux BTF
is loaded even in gen_loader mode if the BPF object contains struct_ops maps.
This provides the necessary type information for generating light skeletons.
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 3a80a018fc7d5..b159faae7f9c6 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -3590,7 +3590,11 @@ static int bpf_object__load_vmlinux_btf(struct bpf_object *obj, bool force)
> int err;
>
> /* btf_vmlinux could be loaded earlier */
> - if (obj->btf_vmlinux || obj->gen_loader)
> + if (obj->btf_vmlinux)
> + return 0;
> +
> + /* only struct_ops maps need btf_vmlinux in gen_loader */
> + if (obj->gen_loader && !obj_maps_need_vmlinux_btf(obj))
> return 0;
[Severity: High]
Does enabling struct_ops for light skeletons result in a generated loader
that works correctly on the target system?
It appears the gen_loader logic statically embeds the build host's
btf_vmlinux_value_type_id into the BPF_MAP_CREATE attributes buffer. Since
BTF IDs vary across kernel versions and configurations, won't passing the
build host's BTF ID to a different target kernel cause the syscall to fail
with -EINVAL or incorrectly associate the map with the wrong kernel structure?
Shouldn't the loader dynamically resolve this ID using bpf_btf_find_by_name_kind
on the target kernel instead?
Additionally, struct_ops maps require their map values to be populated with
the runtime FDs of the loaded BPF programs. Does gen_loader currently have
the necessary logic to emit BPF instructions that read dynamically generated
prog_fds from the loader's stack, patch them into the map value buffer, and
update the map?
Furthermore, does the skel__attach() function generated by
bpftool gen skeleton -L handle struct_ops map attachment? It seems to only
iterate over programs, entirely omitting the struct_ops map attachment that
is normally handled by bpf_map__attach_struct_ops().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260527-libbpf-load-vmlinux-btf-in-gen_loader-mode-v4-0-27daa15487a9@google.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops
2026-05-27 12:52 ` [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops Siddharth Nayyar
2026-05-27 13:25 ` sashiko-bot
@ 2026-05-27 13:39 ` bot+bpf-ci
2026-05-27 14:17 ` Alexei Starovoitov
1 sibling, 1 reply; 13+ messages in thread
From: bot+bpf-ci @ 2026-05-27 13:39 UTC (permalink / raw)
To: sidnayyar, andrii, eddyz87, ast, daniel, martin.lau, memxor, song,
yonghong.song, jolsa
Cc: bpf, linux-kernel, gprocida, maennich, sidnayyar, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3757 bytes --]
> commit 29cedaf6d7d80a7e880a2e639f40fbafcbaf2e65
> Author: Siddharth Nayyar <sidnayyar@google.com>
> Date: Wed May 27 12:52:57 2026 +0000
>
> libbpf: load vmlinux BTF in gen_loader mode for struct_ops
>
> During light skeleton generation (`bpftool gen skeleton -L`), libbpf
> runs in gen_loader mode. Previously, `bpf_object__load_vmlinux_btf()`
> completely bypassed loading the kernel vmlinux BTF (`obj->btf_vmlinux`)
> if `gen_loader` was active.
>
> However, BPF `struct_ops` maps (such as `sched_ext_ops` maps) require
> resolving the kernel-side struct type IDs and member sizes at
> compile/skeleton generation time. Without loading `btf_vmlinux`, libbpf
> cannot query the kernel BTF types, causing light skeleton generation for
> `struct_ops` to fail or omit crucial type information.
>
> Fix this by modifying the check to load `btf_vmlinux` even in
> `gen_loader` mode if the BPF object contains `struct_ops` maps.
>
> Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Alexei Starovoitov requested in v3 review that a selftest be added to
prove struct_ops light skeletons work end-to-end:
"The selftest is mandatory to prove that."
https://lore.kernel.org/bpf/CAADnVQ+AA_fmRw7uRwdL2TMyMbpWeebOQKb=0wvpKiQR6Ee5xQ@mail.gmail.com/
This requirement has not been addressed in v4. Should a selftest be added
to demonstrate that struct_ops light skeletons work correctly with this
change?
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index ab2071fdd3e8..420d37f215ea 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -3544,15 +3544,20 @@ static bool prog_needs_vmlinux_btf(struct bpf_program *prog)
> return false;
> }
>
> -static bool map_needs_vmlinux_btf(struct bpf_map *map)
> +static bool obj_maps_need_vmlinux_btf(const struct bpf_object *obj)
> {
> - return bpf_map__is_struct_ops(map);
> + struct bpf_map *map;
> +
> + bpf_object__for_each_map(map, obj) {
> + if (bpf_map__is_struct_ops(map))
> + return true;
> + }
> + return false;
> }
>
> static bool obj_needs_vmlinux_btf(const struct bpf_object *obj)
> {
> struct bpf_program *prog;
> - struct bpf_map *map;
> int i;
>
> /* CO-RE relocations need kernel BTF, only when btf_custom_path
> @@ -3577,12 +3582,7 @@ static bool obj_needs_vmlinux_btf(const struct bpf_object *obj)
> return true;
> }
>
> - bpf_object__for_each_map(map, obj) {
> - if (map_needs_vmlinux_btf(map))
> - return true;
> - }
> -
> - return false;
> + return obj_maps_need_vmlinux_btf(obj);
> }
>
> static int bpf_object__load_vmlinux_btf(struct bpf_object *obj, bool force)
> @@ -3590,7 +3590,11 @@ static int bpf_object__load_vmlinux_btf(struct bpf_object *obj, bool force)
> int err;
>
> /* btf_vmlinux could be loaded earlier */
> - if (obj->btf_vmlinux || obj->gen_loader)
> + if (obj->btf_vmlinux)
> + return 0;
> +
> + /* only struct_ops maps need btf_vmlinux in gen_loader */
> + if (obj->gen_loader && !obj_maps_need_vmlinux_btf(obj))
> return 0;
>
> if (!force && !obj_needs_vmlinux_btf(obj))
This looks like a bug fix for gen_loader mode with struct_ops maps.
Multiple CI reviews (v1 and v3) noted that this should include:
Fixes: 67234743736a ("libbpf: Generate loader program out of BPF ELF file.")
https://lore.kernel.org/bpf/b4b4ca51ae8e281a37707cbcd2ba8349dabc6c630e96461929be0872fd8ed0e6@mail.kernel.org/
Should this tag be added to indicate it fixes a limitation in the original
gen_loader implementation?
---
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/26512949106
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops
2026-05-27 13:39 ` bot+bpf-ci
@ 2026-05-27 14:17 ` Alexei Starovoitov
0 siblings, 0 replies; 13+ messages in thread
From: Alexei Starovoitov @ 2026-05-27 14:17 UTC (permalink / raw)
To: bot+bpf-ci, sidnayyar, andrii, eddyz87, ast, daniel, martin.lau,
memxor, song, yonghong.song, jolsa
Cc: bpf, linux-kernel, gprocida, maennich, martin.lau, clm,
ihor.solodrai
On Wed May 27, 2026 at 6:39 AM PDT, bot+bpf-ci wrote:
>> commit 29cedaf6d7d80a7e880a2e639f40fbafcbaf2e65
>> Author: Siddharth Nayyar <sidnayyar@google.com>
>> Date: Wed May 27 12:52:57 2026 +0000
>>
>> libbpf: load vmlinux BTF in gen_loader mode for struct_ops
>>
>> During light skeleton generation (`bpftool gen skeleton -L`), libbpf
>> runs in gen_loader mode. Previously, `bpf_object__load_vmlinux_btf()`
>> completely bypassed loading the kernel vmlinux BTF (`obj->btf_vmlinux`)
>> if `gen_loader` was active.
>>
>> However, BPF `struct_ops` maps (such as `sched_ext_ops` maps) require
>> resolving the kernel-side struct type IDs and member sizes at
>> compile/skeleton generation time. Without loading `btf_vmlinux`, libbpf
>> cannot query the kernel BTF types, causing light skeleton generation for
>> `struct_ops` to fail or omit crucial type information.
>>
>> Fix this by modifying the check to load `btf_vmlinux` even in
>> `gen_loader` mode if the BPF object contains `struct_ops` maps.
>>
>> Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
>
> Alexei Starovoitov requested in v3 review that a selftest be added to
> prove struct_ops light skeletons work end-to-end:
>
> "The selftest is mandatory to prove that."
> https://lore.kernel.org/bpf/CAADnVQ+AA_fmRw7uRwdL2TMyMbpWeebOQKb=0wvpKiQR6Ee5xQ@mail.gmail.com/
>
> This requirement has not been addressed in v4. Should a selftest be added
> to demonstrate that struct_ops light skeletons work correctly with this
> change?
thanks bot.
pw-bot: cr
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 2/3] libbpf: zero out btf_key_type_id for STRUCT_OPS maps
2026-05-27 12:52 [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons Siddharth Nayyar
2026-05-27 12:52 ` [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops Siddharth Nayyar
@ 2026-05-27 12:52 ` Siddharth Nayyar
2026-05-27 13:39 ` bot+bpf-ci
2026-05-27 12:52 ` [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader Siddharth Nayyar
2026-05-28 21:35 ` [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons Andrii Nakryiko
3 siblings, 1 reply; 13+ messages in thread
From: Siddharth Nayyar @ 2026-05-27 12:52 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov,
Daniel Borkmann, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa
Cc: bpf, linux-kernel, gprocida, maennich, Siddharth Nayyar
For BPF `STRUCT_OPS` maps (such as `sched_ext_ops` maps), the kernel BPF
subsystem enforces strict map-creation safety validations inside
`map_create()`. That is, if `btf_vmlinux_value_type_id` is set, the
kernel forbids passing any userspace `btf_key_type_id` or
`btf_value_type_id` (they must both be `0`).
However, inside libbpf's map-creation options initialization
(`bpf_object__create_map()`), libbpf zeroed out
`create_attr.btf_value_type_id` but does not zero out
`create_attr.btf_key_type_id`.
Fix this by explicitly zeroing out `create_attr.btf_key_type_id`.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
---
tools/lib/bpf/libbpf.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index b159faae7f9c..f9c653541005 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -5440,6 +5440,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
map->btf_value_type_id = 0;
break;
case BPF_MAP_TYPE_STRUCT_OPS:
+ create_attr.btf_key_type_id = 0;
create_attr.btf_value_type_id = 0;
break;
default:
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v4 2/3] libbpf: zero out btf_key_type_id for STRUCT_OPS maps
2026-05-27 12:52 ` [PATCH v4 2/3] libbpf: zero out btf_key_type_id for STRUCT_OPS maps Siddharth Nayyar
@ 2026-05-27 13:39 ` bot+bpf-ci
0 siblings, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-05-27 13:39 UTC (permalink / raw)
To: sidnayyar, andrii, eddyz87, ast, daniel, martin.lau, memxor, song,
yonghong.song, jolsa
Cc: bpf, linux-kernel, gprocida, maennich, sidnayyar, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3475 bytes --]
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 420d37f215ea..673fa0242b4c 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -5440,6 +5440,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
> map->btf_value_type_id = 0;
> break;
> case BPF_MAP_TYPE_STRUCT_OPS:
> + create_attr.btf_key_type_id = 0;
> create_attr.btf_value_type_id = 0;
> break;
> default:
This change fixes the issue in bpf_object__create_map(), but what about
gen_loader mode?
In bpf_gen__map_create() (tools/lib/bpf/gen_loader.c), when
btf_vmlinux_value_type_id is set for STRUCT_OPS maps, it's not copied
to the attr structure:
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);
/* btf_vmlinux_value_type_id is never copied */
This means gen_loader mode (light skeletons) will pass
btf_vmlinux_value_type_id=0 to the kernel, breaking STRUCT_OPS map
creation. Does this need to be addressed?
Also, the conditional that copies btf_fd in bpf_gen__map_create():
if (map_attr->btf_value_type_id)
move_stack2blob(gen, attr_field(map_create_attr, btf_fd), 4,
stack_off(btf_fd));
This condition evaluates to false for STRUCT_OPS maps since
btf_value_type_id is now explicitly zeroed. Won't this prevent
btf_fd from being copied to the kernel attributes?
The kernel expects a valid btf_fd even for STRUCT_OPS maps (see
kernel/bpf/syscall.c comments: "Even the map's value is a kernel's
struct, the bpf_prog.o must have BTF to begin with to figure out
the corresponding kernel's counter part."). Without btf_fd,
btf_get_by_fd(0) will fail.
It looks like both of these issues are fixed in the next commit
(fe6415fcc857b593808826b2e8d157237162782b), but should they be addressed
in this patch to avoid bisection breakage?
Regarding the commit message:
> libbpf: zero out btf_key_type_id for STRUCT_OPS maps
>
> For BPF `STRUCT_OPS` maps (such as `sched_ext_ops` maps), the kernel BPF
> subsystem enforces strict map-creation safety validations inside
> `map_create()`. That is, if `btf_vmlinux_value_type_id` is set, the
> kernel forbids passing any userspace `btf_key_type_id` or
> `btf_value_type_id` (they must both be `0`).
>
> However, inside libbpf's map-creation options initialization
> (`bpf_object__create_map()`), libbpf zeroed out
> `create_attr.btf_value_type_id` but does not zero out
> `create_attr.btf_key_type_id`.
>
> Fix this by explicitly zeroing out `create_attr.btf_key_type_id`.
>
> Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
This looks like a bug fix but lacks a Fixes: tag. The bug was
introduced when STRUCT_OPS support was added to libbpf. Should this
include:
Fixes: 590a00888250 ("bpf: libbpf: Add STRUCT_OPS support")
Also, Alexei Starovoitov requested a selftest in v3 of the patch series:
"The selftest is mandatory to prove that."
https://lore.kernel.org/bpf/CAADnVQ+AA_fmRw7uRwdL2TMyMbpWeebOQKb=0wvpKiQR6Ee5xQ@mail.gmail.com/
Has this been addressed in v4? Without a selftest proving that
STRUCT_OPS maps work correctly in light skeleton mode, how can we
verify the fix is complete?
---
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/26512949106
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader
2026-05-27 12:52 [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons Siddharth Nayyar
2026-05-27 12:52 ` [PATCH v4 1/3] libbpf: load vmlinux BTF in gen_loader mode for struct_ops Siddharth Nayyar
2026-05-27 12:52 ` [PATCH v4 2/3] libbpf: zero out btf_key_type_id for STRUCT_OPS maps Siddharth Nayyar
@ 2026-05-27 12:52 ` Siddharth Nayyar
2026-05-27 13:39 ` bot+bpf-ci
2026-05-27 14:10 ` sashiko-bot
2026-05-28 21:35 ` [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons Andrii Nakryiko
3 siblings, 2 replies; 13+ messages in thread
From: Siddharth Nayyar @ 2026-05-27 12:52 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov,
Daniel Borkmann, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa
Cc: bpf, linux-kernel, gprocida, maennich, Siddharth Nayyar
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()`) omitted plumbing `btf_vmlinux_value_type_id`.
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. Copying `btf_vmlinux_value_type_id` from the options inside
`bpf_gen__map_create()`.
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@google.com>
---
tools/lib/bpf/gen_loader.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
index cd5c2543f54d..a9be0c241025 100644
--- a/tools/lib/bpf/gen_loader.c
+++ b/tools/lib/bpf/gen_loader.c
@@ -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
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader
2026-05-27 12:52 ` [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader Siddharth Nayyar
@ 2026-05-27 13:39 ` bot+bpf-ci
2026-05-27 14:10 ` sashiko-bot
1 sibling, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-05-27 13:39 UTC (permalink / raw)
To: sidnayyar, andrii, eddyz87, ast, daniel, martin.lau, memxor, song,
yonghong.song, jolsa
Cc: bpf, linux-kernel, gprocida, maennich, sidnayyar, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4716 bytes --]
> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> index 9478b8f78f26..b0d8551272f2 100644
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -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);
Does hardcoding btf_vmlinux_value_type_id here break cross-kernel
portability for STRUCT_OPS light skeletons?
Light skeletons are designed to be portable across different kernel
versions without relying on libbpf at runtime. Because BTF type IDs are
not stable across kernel builds, passing the build machine's BTF ID to
the target kernel might cause the target kernel's sys_bpf(MAP_CREATE) to
resolve to the wrong type or fail entirely with -EINVAL.
Should gen_loader dynamically resolve the target kernel's BTF ID at
runtime, similarly to how it handles attach_btf_id?
>
> map_create_attr = add_data(gen, &attr, attr_size);
Does this also miss plumbing value_type_btf_obj_fd for STRUCT_OPS
interfaces defined in kernel modules?
When a STRUCT_OPS map targets a kernel module, libbpf sets
value_type_btf_obj_fd in bpf_map_create_opts and adds
BPF_F_VTYPE_BTF_OBJ_FD to map_flags. The patch propagates map_flags into
gen_loader's map creation attributes but ignores value_type_btf_obj_fd.
In bpf_gen__map_create(), attr_size is calculated as offsetofend(union
bpf_attr, map_extra), which truncates the struct before
value_type_btf_obj_fd. As a result, the generated loader program passes
the BPF_F_VTYPE_BTF_OBJ_FD flag to the kernel but implicitly provides 0
for the FD. The target kernel attempts to read BTF from fd=0 (stdin) and
rejects the map with -EINVAL.
Furthermore, even if the FD field were included, the loader cannot
hardcode the build machine's module FD; it must dynamically resolve the
module BTF FD on the target machine.
> 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));
--- Commit Message ---
> libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader
>
> 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()`) omitted plumbing `btf_vmlinux_value_type_id`.
> 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. Copying `btf_vmlinux_value_type_id` from the options inside
> `bpf_gen__map_create()`.
> 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@google.com>
This looks like a bug fix for STRUCT_OPS map creation in gen_loader. The
bug was introduced in commit 67234743736a ("libbpf: Generate loader
program out of BPF ELF file.") which created gen_loader.c but omitted
copying btf_vmlinux_value_type_id to the map creation attributes and only
conditionally copied btf_fd when btf_value_type_id was set. This caused
STRUCT_OPS maps (which zero out btf_value_type_id but set
btf_vmlinux_value_type_id) to fail with btf_get_by_fd(0) errors. Should
this include:
Fixes: 67234743736a ("libbpf: Generate loader program out of BPF ELF file.")
---
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/26512949106
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader
2026-05-27 12:52 ` [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader Siddharth Nayyar
2026-05-27 13:39 ` bot+bpf-ci
@ 2026-05-27 14:10 ` sashiko-bot
2026-05-27 14:27 ` Sid Nayyar
1 sibling, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-05-27 14:10 UTC (permalink / raw)
To: Siddharth Nayyar; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] The patch truncates the bpf_attr payload, dropping `value_type_btf_obj_fd`, which breaks module-backed STRUCT_OPS maps in gen_loader.
Pre-existing issues:
- [Critical] Double-close of `inner_map_fd` in the gen_loader cleanup path can hijack and close an unrelated file descriptor.
- [High] The condition to populate `btf_fd` incorrectly omits `btf_key_type_id`, causing map creation to fail if only a key BTF type is defined.
- [High] Memory leak of dynamic arrays `gen->relos`, `gen->ksyms`, and `gen->core_relos` in the error path.
--
commit b58090bc2b448f8a5c71dbba37463b5a9e73fdd5
Author: Siddharth Nayyar <sidnayyar@google.com>
libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader
This commit updates the BPF map creation generator to plumb the kernel-side
structure value type ID (btf_vmlinux_value_type_id) into the map creation
system call attributes. It also modifies the condition for copying the
userspace BTF file descriptor so it populates btf_fd when either
btf_value_type_id or btf_vmlinux_value_type_id is set.
> diff --git a/tools/lib/bpf/gen_loader.c b/tools/lib/bpf/gen_loader.c
> --- a/tools/lib/bpf/gen_loader.c
> +++ b/tools/lib/bpf/gen_loader.c
> @@ -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);
[Severity: High]
Does this code truncate the bpf_attr payload and drop value_type_btf_obj_fd?
At the top of bpf_gen__map_create(), attr_size is calculated as
offsetofend(union bpf_attr, map_extra). However, value_type_btf_obj_fd was
introduced after map_extra in the union. By plumbing btf_vmlinux_value_type_id
here without expanding attr_size, the loader may send a truncated payload to
the kernel.
For module-backed struct_ops maps, libbpf sets the BPF_F_VTYPE_BTF_OBJ_FD flag,
but the kernel would receive value_type_btf_obj_fd = 0 (stdin). Could this
cause btf_get_by_fd(0) to fail and break map creation?
>
> 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)
[Severity: High]
This is a pre-existing issue, but does this condition incorrectly omit
btf_key_type_id?
If a map defines only a BTF key type but no value type, btf_key_type_id will be
non-zero while btf_value_type_id remains zero.
This means gen_loader would skip populating btf_fd in this case, sending
btf_fd = 0 alongside the non-zero btf_key_type_id. Since the kernel strictly
validates that btf_fd must be valid if any BTF type ID is set, will the kernel
reject this with -EINVAL?
> /* 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));
[Severity: Critical]
This is a pre-existing issue, but can the inner map handling later in this
function lead to a double-close regression in the cleanup path?
When emit_sys_close_stack() is called for inner_map_fd, it issues the sys_close
BPF helper but doesn't zero out the memory slot containing the file descriptor
on the loader stack.
If a subsequent command in the loader program fails, the cleanup routine loops
over the loader stack and calls sys_close on any file descriptor greater than
zero. Because the inner_map_fd slot was never cleared, it gets closed a second
time. If another thread opened a new file or socket in the interim, could this
hijack and close the unrelated resource?
[Severity: High]
This is a pre-existing issue, but is there a memory leak in the error path for
the dynamic arrays used during generation?
The arrays gen->relos, gen->ksyms, and gen->core_relos are dynamically allocated
using libbpf_reallocarray(). They are expected to be freed by cleanup_relos()
at the end of bpf_gen__prog_load().
If the loader encounters an error earlier in the process, the program aborts
and bpf_gen__free() is called. However, bpf_gen__free() appears to fail to free
these arrays or call cleanup_relos(), causing a permanent memory leak. For
long-running daemons parsing untrusted objects, could this be exploited to
cause an out-of-memory crash?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260527-libbpf-load-vmlinux-btf-in-gen_loader-mode-v4-0-27daa15487a9@google.com?part=3
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader
2026-05-27 14:10 ` sashiko-bot
@ 2026-05-27 14:27 ` Sid Nayyar
0 siblings, 0 replies; 13+ messages in thread
From: Sid Nayyar @ 2026-05-27 14:27 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Wed, May 27, 2026 at 3:10 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
>
> New issues:
> - [High] The patch truncates the bpf_attr payload, dropping `value_type_btf_obj_fd`, which breaks module-backed STRUCT_OPS maps in gen_loader.
>
> Pre-existing issues:
> - [Critical] Double-close of `inner_map_fd` in the gen_loader cleanup path can hijack and close an unrelated file descriptor.
> - [High] The condition to populate `btf_fd` incorrectly omits `btf_key_type_id`, causing map creation to fail if only a key BTF type is defined.
> - [High] Memory leak of dynamic arrays `gen->relos`, `gen->ksyms`, and `gen->core_relos` in the error path.
There seems to be a lot of feedback to unpack here. I will address
these concerns and refresh the patch series once I have reviewed them
and incorporated the required changes.
Thanks,
Siddharth Nayyar
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons
2026-05-27 12:52 [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons Siddharth Nayyar
` (2 preceding siblings ...)
2026-05-27 12:52 ` [PATCH v4 3/3] libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader Siddharth Nayyar
@ 2026-05-28 21:35 ` Andrii Nakryiko
2026-05-29 10:31 ` Sid Nayyar
3 siblings, 1 reply; 13+ messages in thread
From: Andrii Nakryiko @ 2026-05-28 21:35 UTC (permalink / raw)
To: Siddharth Nayyar
Cc: Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov,
Daniel Borkmann, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, bpf, linux-kernel, gprocida,
maennich
On Wed, May 27, 2026 at 5:53 AM Siddharth Nayyar <sidnayyar@google.com> wrote:
>
> This series enables support for BPF `STRUCT_OPS` maps (such as
> `sched_ext_ops` used by sched_ext) when using light skeletons (i.e.
> `gen_loader` mode).
>
> Previously, generating light skeletons for objects containing
> `STRUCT_OPS` maps would fail or produce incomplete results because
> `gen_loader` lacked support for several key features required by
> `STRUCT_OPS` maps, specifically:
>
> 1. Loading `vmlinux` BTF to resolve kernel-side type information.
> 2. Correctly plumbing `btf_vmlinux_value_type_id` into map creation
> attributes.
> 3. Ensuring `btf_key_type_id` is zeroed out to satisfy kernel safety
> checks.
> 4. Plumbing the userspace BTF FD (`btf_fd`) when
> `btf_vmlinux_value_type_id` is present but `btf_value_type_id` is
> zero (which is always the case for `STRUCT_OPS` maps).
>
> This series addresses these limitations by:
>
> - Loading `vmlinux` BTF in `gen_loader` mode when the BPF object
> contains `struct_ops` maps.
> - Explicitly zeroing out `btf_key_type_id` for `STRUCT_OPS` maps to
> satisfy kernel validations.
> - Plumbing `btf_vmlinux_value_type_id` during map creation in
> `gen_loader`.
this type ID search has to happen on target kernel at runtime,
otherwise your lskel will be bound to work only on exact kernel you
used to run bpftool to generate lskel
> - Fixing `btf_fd` copying logic in `gen_loader` to populate it if either
> `btf_value_type_id` or `btf_vmlinux_value_type_id` is set.
>
> With these changes, it is now possible to generate and use light
> skeletons for BPF programs utilizing `STRUCT_OPS`, such as custom
> sched_ext schedulers.
>
> Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
> ---
> Changes in v4:
> - Load btf_vmlinux in gen_loader mode only when BPF object contains
> STRUCT_OPS map.
> - Link to v3:
> https://lore.kernel.org/r/20260526-libbpf-load-vmlinux-btf-in-gen_loader-mode-v3-0-5b4fa2a5e1a4@google.com
>
> Changes in v3:
> - Remove erroneous change contracting map creation attribute size in
> gen_loader.
> - Link to v2:
> https://lore.kernel.org/r/20260526-libbpf-load-vmlinux-btf-in-gen_loader-mode-v2-0-6750f5859bc6@google.com
>
> Changes in v2:
> - Expand the series to 3 patches to fully support STRUCT_OPS in
> gen_loader.
> - Add a patch to explicitly zero out btf_key_type_id for STRUCT_OPS
> maps.
> - Add a patch to plumb btf_vmlinux_value_type_id and btf_fd in
> gen_loader.
> - Link to v1:
> https://lore.kernel.org/r/20260524-libbpf-load-vmlinux-btf-in-gen_loader-mode-v1-1-6f57f191a7ad@google.com
>
> ---
> Siddharth Nayyar (3):
> libbpf: load vmlinux BTF in gen_loader mode for struct_ops
> libbpf: zero out btf_key_type_id for STRUCT_OPS maps
> libbpf: plumb btf_vmlinux_value_type_id and btf_fd in gen_loader
>
> tools/lib/bpf/gen_loader.c | 3 ++-
> tools/lib/bpf/libbpf.c | 25 +++++++++++++++----------
> 2 files changed, 17 insertions(+), 11 deletions(-)
> ---
> base-commit: c6e99c10fd9855082568cbd71bb2cc5dc90eda53
> change-id: 20260522-libbpf-load-vmlinux-btf-in-gen_loader-mode-4474834aa467
>
> Best regards,
> --
> Siddharth Nayyar <sidnayyar@google.com>
>
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons
2026-05-28 21:35 ` [PATCH v4 0/3] libbpf: support STRUCT_OPS in light skeletons Andrii Nakryiko
@ 2026-05-29 10:31 ` Sid Nayyar
0 siblings, 0 replies; 13+ messages in thread
From: Sid Nayyar @ 2026-05-29 10:31 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: Andrii Nakryiko, Eduard Zingerman, Alexei Starovoitov,
Daniel Borkmann, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, bpf, linux-kernel, gprocida,
maennich
On Thu, May 28, 2026 at 10:35 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Wed, May 27, 2026 at 5:53 AM Siddharth Nayyar <sidnayyar@google.com> wrote:
> >
> > This series enables support for BPF `STRUCT_OPS` maps (such as
> > `sched_ext_ops` used by sched_ext) when using light skeletons (i.e.
> > `gen_loader` mode).
> >
> > Previously, generating light skeletons for objects containing
> > `STRUCT_OPS` maps would fail or produce incomplete results because
> > `gen_loader` lacked support for several key features required by
> > `STRUCT_OPS` maps, specifically:
> >
> > 1. Loading `vmlinux` BTF to resolve kernel-side type information.
> > 2. Correctly plumbing `btf_vmlinux_value_type_id` into map creation
> > attributes.
> > 3. Ensuring `btf_key_type_id` is zeroed out to satisfy kernel safety
> > checks.
> > 4. Plumbing the userspace BTF FD (`btf_fd`) when
> > `btf_vmlinux_value_type_id` is present but `btf_value_type_id` is
> > zero (which is always the case for `STRUCT_OPS` maps).
> >
> > This series addresses these limitations by:
> >
> > - Loading `vmlinux` BTF in `gen_loader` mode when the BPF object
> > contains `struct_ops` maps.
> > - Explicitly zeroing out `btf_key_type_id` for `STRUCT_OPS` maps to
> > satisfy kernel validations.
> > - Plumbing `btf_vmlinux_value_type_id` during map creation in
> > `gen_loader`.
>
> this type ID search has to happen on target kernel at runtime,
> otherwise your lskel will be bound to work only on exact kernel you
> used to run bpftool to generate lskel
Thank you for pointing this out. I realized the flaw in my approach
shortly after sending these patches. You're absolutely right,
performing the type ID search at generation time would incorrectly
bind the light skeleton to the specific kernel used during the bpftool
run.
I am currently working on a more robust solution that handles the
STRUCT_OPS type ID resolution on the target kernel at runtime. I’ll
follow up with a revised series once I have properly addressed this.
Regards,
Siddharth Nayyar
^ permalink raw reply [flat|nested] 13+ messages in thread