From: sashiko-bot@kernel.org
To: "Andrey Grodzovsky" <andrey.grodzovsky@crowdstrike.com>,
bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next v8 3/7] libbpf: BPF programs manual loading and attaching
Date: Sat, 24 Oct 2026 02:03:48 +0000 [thread overview]
Message-ID: <sashiko-outbox-183505@kernel.org> (raw)
In-Reply-To: <20261024014514.2959514-4-andrey.grodzovsky@crowdstrike.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] libbpf: bpf_object__free_relocs() frees reloc_desc for manual progs
- [Medium] libbpf: bpf_program__load() reload fails for jump and func_ptr maps
- [Medium] libbpf: bpf_program__load() allows loading after failed prepare/load
- [Medium] libbpf: bpf_map__init_kern_struct_ops() clobbers manual load strategy
--
commit 2dd11a6f941c4fbe6a6982f6a14ae6a049d19390
Author: Slava Imameev <slava.imameev@crowdstrike.com>
libbpf: BPF programs manual loading and attaching
This commit adds BPF_PROG_LOAD_STRATEGY_MANUAL and bpf_program__load() so
that designated BPF programs can be skipped during bpf_object__load() and
loaded, unloaded, and reloaded on demand afterwards.
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index e4cdbc2f467db..1d5d59b95c4e8 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -882,12 +883,30 @@ void bpf_program__unload(struct bpf_program *prog)
> zfree(&prog->subprogs);
> }
>
> +void bpf_program__unload(struct bpf_program *prog)
> +{
> + if (!prog)
> + return;
> +
> + /*
> + * MANUAL programs retain their data here so bpf_program__load()
> + * can reload them later; object teardown paths call
> + * bpf_program_unload_full() instead to always release it fully.
> + */
> + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
> + zclose(prog->fd);
> + return;
> + }
[Severity: Medium]
What happens when a BPF_PROG_LOAD_STRATEGY_MANUAL program that uses jump
tables or indirect function call tables in read-only data is unloaded here
and then reloaded via bpf_program__load()?
During bpf_object_prepare() -> bpf_object__relocate() ->
bpf_object__relocate_data(), libbpf creates per-program frozen internal
maps once via create_jt_map() (for RELO_INSN_ARRAY) and
create_func_ptr_map() (when map->autocreate && map->func_ptr_cnt &&
prog->has_callx), patching their file descriptors directly into
prog->insns:
tools/lib/bpf/libbpf.c:bpf_object__relocate_data() {
...
if (map->autocreate && map->func_ptr_cnt && prog->has_callx) {
int map_fd;
/* the program gets its own map with pointers to its functions */
map_fd = create_func_ptr_map(obj, prog, relo->map_idx);
...
insn[0].imm = map_fd;
}
...
}
On the first bpf_program__load() call, the kernel verifier claims these
maps permanently:
1. For jump table maps, bpf_insn_array_init() sets insn_array->used to 1 via
atomic_xchg(&insn_array->used, 1), which is only released on verifier
failure and never cleared when a loaded program is later freed.
2. For function pointer maps, resolve_func_ptrs() sets BPF_MAP_USER_PATCHED
on map->user and overwrites the subprogram byte offsets in the frozen
map in-place with JITed kernel addresses; __bpf_free_used_maps() does
not clear BPF_MAP_USER_PATCHED or restore the offsets when the program
is closed.
When bpf_program__unload() closes prog->fd and bpf_program__load() is
called again, prog->insns still references those same map file descriptors.
Won't the kernel verifier reject the reload with -EBUSY in
bpf_insn_array_init() or bpf_map_claim()?
[ ... ]
> @@ -1228,6 +1247,14 @@ static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)
> if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
> continue;
>
> + /*
> + * If the user set the load strategy to MANUAL, don't guess it
> + * from map autocreate: a MANUAL program is loaded explicitly
> + * with bpf_program__load(), which rejects any other strategy.
> + */
> + if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
> + continue;
[Severity: Medium]
Can bpf_map__init_kern_struct_ops() overwrite prog->load_strategy before
bpf_object_adjust_struct_ops_autoload() reaches this check?
In bpf_object_prepare(), bpf_object__init_kern_struct_ops_maps() ->
bpf_map__init_kern_struct_ops() runs immediately before
bpf_object_adjust_struct_ops_autoload(). Inside
bpf_map__init_kern_struct_ops(), if a struct_ops member is absent in the
running kernel's BTF and zeroed in shadow data, or if the slot was cleared
or replaced in shadow data, it unconditionally resets load_strategy to
BPF_PROG_LOAD_STRATEGY_DISABLED:
tools/lib/bpf/libbpf.c:bpf_map__init_kern_struct_ops() {
...
if (!kern_member) {
...
if (st_ops->progs[i]) {
st_ops->progs[i]->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
st_ops->progs[i] = NULL;
}
...
}
...
if (btf_is_ptr(mtype)) {
prog = *(void **)mdata;
if (st_ops->progs[i] && st_ops->progs[i] != prog)
st_ops->progs[i]->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
...
}
If the user explicitly set BPF_PROG_LOAD_STRATEGY_MANUAL on that program via
bpf_program__set_load_strategy(), won't bpf_map__init_kern_struct_ops()
clobber it to BPF_PROG_LOAD_STRATEGY_DISABLED (or cause
bpf_object_adjust_struct_ops_autoload() to set it to
BPF_PROG_LOAD_STRATEGY_AUTO if referenced by another autocreated map)?
[ ... ]
> @@ -16298,3 +16430,32 @@ enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program
> {
> return prog->load_strategy;
> }
> +
> +int
> +bpf_program__load(struct bpf_program *prog)
> +{
> + int err;
> + struct bpf_object *obj = prog->obj;
> +
> + /*
> + * This function cannot be called before OBJ_PREPARED -
> + * Program's data is initialized on object prepare.
> + */
> + if (obj->state < OBJ_PREPARED)
> + return libbpf_err(-EINVAL);
[Severity: Medium]
Is checking obj->state < OBJ_PREPARED sufficient if bpf_object_prepare() or
bpf_object_load() previously failed?
When bpf_object_prepare() or bpf_object_load() fails, it tears down the
object via bpf_object_unload(obj) and sets obj->state = OBJ_LOADED:
tools/lib/bpf/libbpf.c:bpf_object_prepare() {
...
if (err) {
bpf_object_unpin(obj);
bpf_object_unload(obj);
obj->state = OBJ_LOADED;
return err;
}
}
Because OBJ_LOADED is greater than OBJ_PREPARED, a subsequent call to
bpf_program__load() passes this state check and attempts to load the
program after bpf_object_unload() has already closed all map file
descriptors and called bpf_program_unload_full() (freeing prog->func_info,
prog->line_info, and prog->subprogs to NULL while leaving their counts
non-zero, or before prog->insns was relocated and sanitized).
> +
> + if (prog_is_subprog(obj, prog) || prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
> + return libbpf_err(-EINVAL);
> +
> + if (prog->fd >= 0)
> + return libbpf_err(-EBUSY);
> +
> + err = bpf_object_load_prog(obj, prog, prog->insns, prog->insns_cnt,
> + obj->license, obj->kern_version, &prog->fd);
> + if (err) {
> + pr_warn("prog '%s': failed to load: %s\n", prog->name, errstr(err));
> + return libbpf_err(err);
> + }
[Severity: Medium]
Could CO-RE relocation failure diagnostics in the verifier log be lost here
when bpf_program__load() is called after bpf_object__load()?
During bpf_object_prepare() -> bpf_object__relocate() ->
bpf_object__relocate_core(), CO-RE relocations are recorded in
prog->reloc_desc for all non-disabled programs, and unguarded failing CO-RE
instructions are poisoned with call 195896080.
However, at the end of bpf_object__load_progs(), libbpf calls
bpf_object__free_relocs(obj), which frees prog->reloc_desc and zeroes
prog->nr_reloc for all programs in the object, including
BPF_PROG_LOAD_STRATEGY_MANUAL programs that were skipped during the
autoload loop:
tools/lib/bpf/libbpf.c:bpf_object__free_relocs() {
...
for (i = 0; i < obj->nr_programs; i++) {
prog = &obj->programs[i];
zfree(&prog->reloc_desc);
prog->nr_reloc = 0;
}
}
When bpf_program__load() later calls bpf_object_load_prog() and
verification fails on a poisoned CO-RE relocation, fixup_verifier_log() ->
fixup_log_failed_core_relo() -> find_relo_core() searches prog->reloc_desc
up to prog->nr_reloc, finds nothing, and leaves the raw poisoned call error
in the verifier log instead of reporting the failed CO-RE relocation
details.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261024014514.2959514-1-andrey.grodzovsky@crowdstrike.com?part=3
next prev parent reply other threads:[~2026-10-24 2:03 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 1:45 [PATCH bpf-next v8 0/7] libbpf: BPF program manual loading Andrey Grodzovsky
2026-10-24 1:45 ` [PATCH bpf-next v8 1/7] libbpf: Reject gen_loader set after PREPARE step Andrey Grodzovsky
2026-10-24 1:45 ` [PATCH bpf-next v8 2/7] libbpf: BPF program load strategy enum Andrey Grodzovsky
2026-10-24 1:45 ` [PATCH bpf-next v8 3/7] libbpf: BPF programs manual loading and attaching Andrey Grodzovsky
2026-10-24 2:03 ` sashiko-bot [this message]
2026-10-24 3:43 ` Alexei Starovoitov
2026-10-28 2:06 ` Andrey Grodzovsky
2026-10-24 1:45 ` [PATCH bpf-next v8 4/7] libbpf: Support declarative manual load via SEC("!...") prefix Andrey Grodzovsky
2026-10-24 1:45 ` [PATCH bpf-next v8 5/7] selftests/bpf: Cover BPF program load strategy transitions Andrey Grodzovsky
2026-10-24 1:54 ` sashiko-bot
2026-10-24 2:23 ` bot+bpf-ci
2026-10-24 1:45 ` [PATCH bpf-next v8 6/7] selftests/bpf: Cover BPF program manual loading Andrey Grodzovsky
2026-10-24 1:45 ` [PATCH bpf-next v8 7/7] selftests/bpf: Convert veristat to BPF_PROG_LOAD_STRATEGY_MANUAL Andrey Grodzovsky
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-183505@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=andrey.grodzovsky@crowdstrike.com \
--cc=bpf@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox