BPF List
 help / color / mirror / Atom feed
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

  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