| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/10 15:05 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "All modified files are located within tools/ (specifically tools/lib/bpf/ and tools/testing/selftests/bpf/), affecting only user-space libbpf and selftests/veristat. They do not alter the compiled Linux kernel image (vmlinux) or kernel modules, and thus are not reachable or suitable for kernel fuzzing with syzkaller.",
"WorthFuzzing": false
} |
| 1/1 | 2026/10/10 15:05 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit f6db0d7a9b8584feca495a8d8f8b169d53252434\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Oct 10 15:05:53 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c\nindex cb09ded907732..043f220ebb136 100644\n--- a/tools/lib/bpf/libbpf.c\n+++ b/tools/lib/bpf/libbpf.c\n@@ -494,7 +494,7 @@ struct bpf_program {\n \tstruct bpf_object *obj;\n \n \tint fd;\n-\tbool autoload;\n+\tenum bpf_prog_load_strategy load_strategy;\n \tbool autoattach;\n \tbool sym_global;\n \t/* the program or a function that it calls has callx */\n@@ -758,6 +758,7 @@ struct bpf_object {\n \n \tbool has_subcalls;\n \tbool has_rodata;\n+\tbool has_manual_progs;\n \n \tstruct bpf_gen *gen_loader;\n \n@@ -864,7 +865,7 @@ static Elf_Data *elf_sec_data(const struct bpf_object *obj, Elf_Scn *scn);\n static Elf64_Sym *elf_sym_by_idx(const struct bpf_object *obj, size_t idx);\n static Elf64_Rel *elf_rel_by_idx(Elf_Data *data, size_t idx);\n \n-void bpf_program__unload(struct bpf_program *prog)\n+static void bpf_program_unload_full(struct bpf_program *prog)\n {\n \tif (!prog)\n \t\treturn;\n@@ -876,12 +877,30 @@ void bpf_program__unload(struct bpf_program *prog)\n \tzfree(\u0026prog-\u003esubprogs);\n }\n \n+void bpf_program__unload(struct bpf_program *prog)\n+{\n+\tif (!prog)\n+\t\treturn;\n+\n+\t/*\n+\t * MANUAL programs retain their data here so bpf_program__load()\n+\t * can reload them later; object teardown paths call\n+\t * bpf_program_unload_full() instead to always release it fully.\n+\t */\n+\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {\n+\t\tzclose(prog-\u003efd);\n+\t\treturn;\n+\t}\n+\n+\tbpf_program_unload_full(prog);\n+}\n+\n static void bpf_program__exit(struct bpf_program *prog)\n {\n \tif (!prog)\n \t\treturn;\n \n-\tbpf_program__unload(prog);\n+\tbpf_program_unload_full(prog);\n \tzfree(\u0026prog-\u003ename);\n \tzfree(\u0026prog-\u003esec_name);\n \tzfree(\u0026prog-\u003einsns);\n@@ -949,13 +968,21 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,\n \t/* libbpf's convention for SEC(\"?abc...\") is that it's just like\n \t * SEC(\"abc...\") but the corresponding bpf_program starts out with\n \t * autoload set to false.\n+\t *\n+\t * Similarly, SEC(\"!abc...\") marks the program for manual loading:\n+\t * it is skipped by the bulk auto-load pass and must be explicitly\n+\t * loaded via bpf_program__load().\n \t */\n \tif (sec_name[0] == '?') {\n-\t\tprog-\u003eautoload = false;\n+\t\tprog-\u003eload_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;\n \t\t/* from now on forget there was ? in section name */\n \t\tsec_name++;\n+\t} else if (sec_name[0] == '!') {\n+\t\tprog-\u003eload_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;\n+\t\t/* from now on forget there was ! in section name */\n+\t\tsec_name++;\n \t} else {\n-\t\tprog-\u003eautoload = true;\n+\t\tprog-\u003eload_strategy = BPF_PROG_LOAD_STRATEGY_AUTO;\n \t}\n \n \tprog-\u003eautoattach = true;\n@@ -1222,6 +1249,15 @@ static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)\n \t\tif (prog-\u003etype != BPF_PROG_TYPE_STRUCT_OPS)\n \t\t\tcontinue;\n \n+\t\t/*\n+\t\t * We avoid this for MANUAL programs to allow veristat\n+\t\t * conversion from clone() to MANUAL loading. Setting AUTO\n+\t\t * for a program would cause it to be rejected during manual\n+\t\t * loading.\n+\t\t */\n+\t\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)\n+\t\t\tcontinue;\n+\n \t\tfor (j = 0; j \u003c obj-\u003enr_maps; ++j) {\n \t\t\tconst struct btf_type *type;\n \n@@ -1242,7 +1278,8 @@ static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)\n \t\t\t}\n \t\t}\n \t\tif (use_cnt)\n-\t\t\tprog-\u003eautoload = should_load;\n+\t\t\tprog-\u003eload_strategy = should_load ? BPF_PROG_LOAD_STRATEGY_AUTO\n+\t\t\t\t: BPF_PROG_LOAD_STRATEGY_DISABLED;\n \t}\n \n \treturn 0;\n@@ -1333,7 +1370,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)\n \t\t\t\t * then bpf_object_adjust_struct_ops_autoload() will update its\n \t\t\t\t * autoload accordingly.\n \t\t\t\t */\n-\t\t\t\tst_ops-\u003eprogs[i]-\u003eautoload = false;\n+\t\t\t\tst_ops-\u003eprogs[i]-\u003eload_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;\n \t\t\t\tst_ops-\u003eprogs[i] = NULL;\n \t\t\t}\n \n@@ -1371,7 +1408,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)\n \t\t\t * if user replaced it with another program or NULL\n \t\t\t */\n \t\t\tif (st_ops-\u003eprogs[i] \u0026\u0026 st_ops-\u003eprogs[i] != prog)\n-\t\t\t\tst_ops-\u003eprogs[i]-\u003eautoload = false;\n+\t\t\t\tst_ops-\u003eprogs[i]-\u003eload_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;\n \n \t\t\t/* Update the value from the shadow type */\n \t\t\tst_ops-\u003eprogs[i] = prog;\n@@ -3806,7 +3843,7 @@ static bool obj_needs_vmlinux_btf(const struct bpf_object *obj)\n \t}\n \n \tbpf_object__for_each_program(prog, obj) {\n-\t\tif (!prog-\u003eautoload)\n+\t\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)\n \t\t\tcontinue;\n \t\tif (prog_needs_vmlinux_btf(prog))\n \t\t\treturn true;\n@@ -5867,6 +5904,20 @@ static int init_prog_array_slots(struct bpf_object *obj, struct bpf_map *map)\n \t\t\tcontinue;\n \n \t\ttarg_prog = map-\u003einit_slots[i];\n+\n+\t\t/*\n+\t\t * Be careful about manual progs, reject if they weren't loaded before\n+\t\t * this step to not insert invalid FDs into the map, otherwise we will\n+\t\t * get a much less clear rejection from the kernel on the element update\n+\t\t * below.\n+\t\t */\n+\t\tif (targ_prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL \u0026\u0026\n+\t\t targ_prog-\u003efd \u003c 0) {\n+\t\t\tpr_warn(\"map '%s': slot [%u] prog '%s' is MANUAL but not loaded\\n\",\n+\t\t\t\tmap-\u003ename, i, targ_prog-\u003ename);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n \t\tfd = bpf_program__fd(targ_prog);\n \n \t\terr = bpf_map_update_elem(map-\u003efd, \u0026i, \u0026fd, 0);\n@@ -6495,7 +6546,7 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)\n \t\t\t/* no need to apply CO-RE relocation if the program is\n \t\t\t * not going to be loaded\n \t\t\t */\n-\t\t\tif (!prog-\u003eautoload)\n+\t\t\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)\n \t\t\t\tcontinue;\n \n \t\t\t/* adjust insn_idx from section frame of reference to the local\n@@ -8166,7 +8217,7 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat\n \t\t */\n \t\tif (prog_is_subprog(obj, prog))\n \t\t\tcontinue;\n-\t\tif (!prog-\u003eautoload)\n+\t\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)\n \t\t\tcontinue;\n \n \t\terr = bpf_object__relocate_calls(obj, prog);\n@@ -8202,7 +8253,7 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat\n \t\tprog = \u0026obj-\u003eprograms[i];\n \t\tif (prog_is_subprog(obj, prog))\n \t\t\tcontinue;\n-\t\tif (!prog-\u003eautoload)\n+\t\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)\n \t\t\tcontinue;\n \n \t\t/* Process data relos for main programs */\n@@ -9241,8 +9292,8 @@ bpf_object__load_progs(struct bpf_object *obj, int log_level)\n \t\tprog = \u0026obj-\u003eprograms[i];\n \t\tif (prog_is_subprog(obj, prog))\n \t\t\tcontinue;\n-\t\tif (!prog-\u003eautoload) {\n-\t\t\tpr_debug(\"prog '%s': skipped loading\\n\", prog-\u003ename);\n+\t\tif (prog-\u003eload_strategy != BPF_PROG_LOAD_STRATEGY_AUTO) {\n+\t\t\tpr_debug(\"prog '%s': skipped auto-loading\\n\", prog-\u003ename);\n \t\t\tcontinue;\n \t\t}\n \t\tprog-\u003elog_level |= log_level;\n@@ -9270,6 +9321,8 @@ static int bpf_object_prepare_progs(struct bpf_object *obj)\n \n \tfor (i = 0; i \u003c obj-\u003enr_programs; i++) {\n \t\tprog = \u0026obj-\u003eprograms[i];\n+\t\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)\n+\t\t\tobj-\u003ehas_manual_progs = true;\n \t\terr = bpf_object__sanitize_prog(obj, prog);\n \t\tif (err)\n \t\t\treturn err;\n@@ -9296,6 +9349,21 @@ static int bpf_object_init_progs(struct bpf_object *obj, const struct bpf_object\n \t\tprog-\u003etype = prog-\u003esec_def-\u003eprog_type;\n \t\tprog-\u003eexpected_attach_type = prog-\u003esec_def-\u003eexpected_attach_type;\n \n+\t\t/*\n+\t\t * struct_ops programs declared SEC(\"!...\") are rejected\n+\t\t * here at declarative parse time for manual loading.\n+\t\t * bpf_program__set_load_strategy() does allow MANUAL for struct_ops\n+\t\t * via its imperative API, but only as a narrow accommodation for\n+\t\t * veristat, which needs to verify each program in isolation\n+\t\t * without ever calling bpf_object__load().\n+\t\t */\n+\t\tif (prog-\u003etype == BPF_PROG_TYPE_STRUCT_OPS \u0026\u0026\n+\t\t prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {\n+\t\t\tpr_warn(\"prog '%s': struct_ops programs do not support manual loading\\n\",\n+\t\t\t\tprog-\u003ename);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n \t\t/* sec_def can have custom callback which should be called\n \t\t * after bpf_program is initialized to adjust its properties\n \t\t */\n@@ -9496,7 +9564,7 @@ static int bpf_object_unload(struct bpf_object *obj)\n \t}\n \n \tfor (i = 0; i \u003c obj-\u003enr_programs; i++)\n-\t\tbpf_program__unload(\u0026obj-\u003eprograms[i]);\n+\t\tbpf_program_unload_full(\u0026obj-\u003eprograms[i]);\n \n \treturn 0;\n }\n@@ -9887,7 +9955,7 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,\n \treturn 0;\n }\n \n-static void bpf_map_prepare_vdata(const struct bpf_map *map)\n+static int bpf_map_prepare_vdata(const struct bpf_map *map)\n {\n \tconst struct btf_type *type;\n \tstruct bpf_struct_ops *st_ops;\n@@ -9903,16 +9971,32 @@ static void bpf_map_prepare_vdata(const struct bpf_map *map)\n \t\tif (!prog)\n \t\t\tcontinue;\n \n+\t\t/*\n+\t\t * Manual struct_ops programs don't get along well with manual\n+\t\t * loading: all the progs belonging to the same struct_ops must\n+\t\t * load and unload together, which is the opposite of the manual\n+\t\t * load concept.\n+\t\t * Still, we allow manual progs here as long as they were loaded\n+\t\t * before this step ast this still will work.\n+\t\t */\n+\t\tif (prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL \u0026\u0026 prog-\u003efd \u003c 0) {\n+\t\t\tpr_warn(\"map '%s': struct_ops member prog '%s' is MANUAL but not loaded\\n\",\n+\t\t\t\tmap-\u003ename, prog-\u003ename);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n \t\tprog_fd = bpf_program__fd(prog);\n \t\tkern_data = st_ops-\u003ekern_vdata + st_ops-\u003ekern_func_off[i];\n \t\t*(unsigned long *)kern_data = prog_fd;\n \t}\n+\n+\treturn 0;\n }\n \n static int bpf_object_prepare_struct_ops(struct bpf_object *obj)\n {\n \tstruct bpf_map *map;\n-\tint i;\n+\tint i, err;\n \n \tfor (i = 0; i \u003c obj-\u003enr_maps; i++) {\n \t\tmap = \u0026obj-\u003emaps[i];\n@@ -9923,7 +10007,9 @@ static int bpf_object_prepare_struct_ops(struct bpf_object *obj)\n \t\tif (!map-\u003eautocreate)\n \t\t\tcontinue;\n \n-\t\tbpf_map_prepare_vdata(map);\n+\t\terr = bpf_map_prepare_vdata(map);\n+\t\tif (err)\n+\t\t\treturn err;\n \t}\n \n \treturn 0;\n@@ -9939,10 +10025,19 @@ static void bpf_object_unpin(struct bpf_object *obj)\n \t\t\tbpf_map__unpin(\u0026obj-\u003emaps[i], NULL);\n }\n \n-static void bpf_object_cleanup_btf(struct bpf_object *obj)\n+static void bpf_object_cleanup_btf(struct bpf_object *obj, bool force)\n {\n \tint i;\n \n+\t/*\n+\t * Module BTF fds and btf_vmlinux are still needed by\n+\t * programs that have not been manually loaded yet, so defer freeing\n+\t * them to the end of the object lifetime, unless the caller forces\n+\t * immediate cleanup.\n+\t */\n+\tif (!force \u0026\u0026 obj-\u003ehas_manual_progs)\n+\t\treturn;\n+\n \t/* clean up module BTFs */\n \tfor (i = 0; i \u003c obj-\u003ebtf_module_cnt; i++) {\n \t\tclose(obj-\u003ebtf_modules[i].fd);\n@@ -9959,13 +10054,38 @@ static void bpf_object_cleanup_btf(struct bpf_object *obj)\n \tobj-\u003ebtf_vmlinux = NULL;\n }\n \n-static void bpf_object_post_load_cleanup(struct bpf_object *obj)\n+static void bpf_object_post_load_cleanup(struct bpf_object *obj, bool force)\n {\n-\t/* clean up fd_array */\n-\tzfree(\u0026obj-\u003efd_array);\n+\t/*\n+\t * The fd array is needed for manually loaded programs,\n+\t * so defer freeing it in that case to the end of the object lifetime.\n+\t */\n+\tif (force || !obj-\u003ehas_manual_progs)\n+\t\tzfree(\u0026obj-\u003efd_array);\n \n \t/* clean up BTF */\n-\tbpf_object_cleanup_btf(obj);\n+\tbpf_object_cleanup_btf(obj, force);\n+}\n+\n+static int bpf_object_check_gen_loader(struct bpf_object *obj)\n+{\n+\tstruct bpf_program *prog;\n+\n+\tif (!obj-\u003egen_loader)\n+\t\treturn 0;\n+\n+\t/*\n+\t * gen_loader only records programs loaded by bpf_object__load_progs(),\n+\t * which skips MANUAL ones.\n+\t */\n+\tbpf_object__for_each_program(prog, obj) {\n+\t\tif (prog-\u003eload_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)\n+\t\t\tcontinue;\n+\t\tpr_warn(\"prog '%s': gen_loader does not support manually-loaded programs\\n\",\n+\t\t\tprog-\u003ename);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\treturn 0;\n }\n \n static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_path)\n@@ -9977,7 +10097,8 @@ static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_pat\n \t\treturn -EINVAL;\n \t}\n \n-\terr = bpf_object_prepare_token(obj);\n+\terr = bpf_object_check_gen_loader(obj);\n+\terr = err ? : bpf_object_prepare_token(obj);\n \terr = err ? : bpf_object__probe_loading(obj);\n \terr = err ? : bpf_object__load_vmlinux_btf(obj, false);\n \terr = err ? : bpf_object__resolve_externs(obj, obj-\u003ekconfig);\n@@ -10032,7 +10153,8 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch\n \t\t\t prog_has_callx(\u0026obj-\u003eprograms[i]))\n \t\t\t\ttext_has_callx = true;\n \t\tfor (i = 0; i \u003c obj-\u003enr_programs; i++)\n-\t\t\tif (obj-\u003eprograms[i].autoload \u0026\u0026 !prog_is_subprog(obj, \u0026obj-\u003eprograms[i]) \u0026\u0026\n+\t\t\tif (obj-\u003eprograms[i].load_strategy != BPF_PROG_LOAD_STRATEGY_DISABLED \u0026\u0026\n+\t\t\t !prog_is_subprog(obj, \u0026obj-\u003eprograms[i]) \u0026\u0026\n \t\t\t (text_has_callx || prog_has_callx(\u0026obj-\u003eprograms[i])))\n \t\t\t\tnr_progs++;\n \t\tbpf_gen__init(obj-\u003egen_loader, obj-\u003elog_level | extra_log_level,\n@@ -10059,7 +10181,7 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch\n \t\t\terr = bpf_gen__finish(obj-\u003egen_loader, obj-\u003enr_programs, obj-\u003enr_maps);\n \t}\n \n-\tbpf_object_post_load_cleanup(obj);\n+\tbpf_object_post_load_cleanup(obj, false);\n \tobj-\u003estate = OBJ_LOADED; /* doesn't matter if successfully or not */\n \n \tif (err) {\n@@ -10527,7 +10649,7 @@ void bpf_object__close(struct bpf_object *obj)\n \t * bpf_object__load(), we need to clean up stuff that is normally\n \t * cleaned up at the end of loading step\n \t */\n-\tbpf_object_post_load_cleanup(obj);\n+\tbpf_object_post_load_cleanup(obj, true);\n \n \tusdt_manager_free(obj-\u003eusdt_man);\n \tobj-\u003eusdt_man = NULL;\n@@ -10536,7 +10658,6 @@ void bpf_object__close(struct bpf_object *obj)\n \tbpf_object__elf_finish(obj);\n \tbpf_object_unload(obj);\n \tbtf__free(obj-\u003ebtf);\n-\tbtf__free(obj-\u003ebtf_vmlinux);\n \tbtf_ext__free(obj-\u003ebtf_ext);\n \n \tfor (i = 0; i \u003c obj-\u003enr_maps; i++)\n@@ -10637,6 +10758,9 @@ int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts)\n \t\treturn libbpf_err(-EFAULT);\n \tif (!OPTS_VALID(opts, gen_loader_opts))\n \t\treturn libbpf_err(-EINVAL);\n+\tif (obj-\u003estate \u003e= OBJ_PREPARED)\n+\t\treturn libbpf_err(-EINVAL);\n+\n \tgen = calloc(1, sizeof(*gen));\n \tif (!gen)\n \t\treturn libbpf_err(-ENOMEM);\n@@ -10713,16 +10837,13 @@ const char *bpf_program__section_name(const struct bpf_program *prog)\n \n bool bpf_program__autoload(const struct bpf_program *prog)\n {\n-\treturn prog-\u003eautoload;\n+\treturn prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_AUTO;\n }\n \n int bpf_program__set_autoload(struct bpf_program *prog, bool autoload)\n {\n-\tif (prog-\u003eobj-\u003estate \u003e= OBJ_LOADED)\n-\t\treturn libbpf_err(-EINVAL);\n-\n-\tprog-\u003eautoload = autoload;\n-\treturn 0;\n+\treturn bpf_program__set_load_strategy(prog,\n+\t\tautoload ? BPF_PROG_LOAD_STRATEGY_AUTO : BPF_PROG_LOAD_STRATEGY_DISABLED);\n }\n \n bool bpf_program__autoattach(const struct bpf_program *prog)\n@@ -10862,7 +10983,13 @@ __u32 bpf_program__log_level(const struct bpf_program *prog)\n \n int bpf_program__set_log_level(struct bpf_program *prog, __u32 log_level)\n {\n-\tif (prog-\u003eobj-\u003estate \u003e= OBJ_LOADED)\n+\t/*\n+\t * Here and below, OBJ_LOADED is not a relevant restriction for\n+\t * manual programs as those can load after the bpf object is\n+\t * already loaded.\n+\t */\n+\tif (prog-\u003eobj-\u003estate \u003e= OBJ_LOADED \u0026\u0026\n+\t prog-\u003eload_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)\n \t\treturn libbpf_err(-EBUSY);\n \n \tprog-\u003elog_level = log_level;\n@@ -10881,7 +11008,8 @@ int bpf_program__set_log_buf(struct bpf_program *prog, char *log_buf, size_t log\n \t\treturn libbpf_err(-EINVAL);\n \tif (log_size \u003e UINT_MAX)\n \t\treturn libbpf_err(-EINVAL);\n-\tif (prog-\u003eobj-\u003estate \u003e= OBJ_LOADED)\n+\tif (prog-\u003eobj-\u003estate \u003e= OBJ_LOADED \u0026\u0026\n+\t prog-\u003eload_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)\n \t\treturn libbpf_err(-EBUSY);\n \n \tprog-\u003elog_buf = log_buf;\n@@ -13661,7 +13789,7 @@ static int collect_func_ids_by_glob(const struct bpf_program *prog, const char *\n \terr = collect_btf_func_ids_by_glob(btf, pattern, ids);\n \n cleanup:\n-\tbpf_object_cleanup_btf(obj);\n+\tbpf_object_cleanup_btf(obj, false);\n \treturn err;\n }\n \n@@ -16105,7 +16233,7 @@ int bpf_object__attach_skeleton(struct bpf_object_skeleton *s)\n \t\tstruct bpf_program *prog = *prog_skel-\u003eprog;\n \t\tstruct bpf_link **link = prog_skel-\u003elink;\n \n-\t\tif (!prog-\u003eautoload || !prog-\u003eautoattach)\n+\t\tif (prog-\u003eload_strategy != BPF_PROG_LOAD_STRATEGY_AUTO || !prog-\u003eautoattach)\n \t\t\tcontinue;\n \n \t\t/* auto-attaching not supported for this program */\n@@ -16215,3 +16343,77 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)\n \tfree(s-\u003eprogs);\n \tfree(s);\n }\n+\n+int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)\n+{\n+\tstruct bpf_object *obj = prog-\u003eobj;\n+\n+\t/*\n+\t * obj-\u003ehas_manual_prog is set once in bpf_object_prepare_progs()\n+\t * and never recomputed; once the object is prepared, no transition\n+\t * into or out of MANUAL may change which programs are MANUAL,\n+\t * regardless of direction. AUTO\u003c-\u003eDISABLED transitions never touch\n+\t * MANUAL and keep the looser, pre-existing OBJ_LOADED gate.\n+\t */\n+\tif (strategy == BPF_PROG_LOAD_STRATEGY_MANUAL ||\n+\t prog-\u003eload_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {\n+\t\tif (obj-\u003estate \u003e= OBJ_PREPARED)\n+\t\t\treturn libbpf_err(-EINVAL);\n+\t} else if (obj-\u003estate \u003e= OBJ_LOADED) {\n+\t\treturn libbpf_err(-EINVAL);\n+\t}\n+\n+\tif (strategy == prog-\u003eload_strategy)\n+\t\treturn 0;\n+\n+\tswitch (strategy) {\n+\tcase BPF_PROG_LOAD_STRATEGY_DISABLED:\n+\tcase BPF_PROG_LOAD_STRATEGY_AUTO:\n+\t\tprog-\u003eload_strategy = strategy;\n+\t\tbreak;\n+\tcase BPF_PROG_LOAD_STRATEGY_MANUAL:\n+\t\tif (prog_is_subprog(obj, prog))\n+\t\t\treturn libbpf_err(-EINVAL);\n+\n+\t\tprog-\u003eload_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn libbpf_err(-EINVAL);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program *prog)\n+{\n+\treturn prog-\u003eload_strategy;\n+}\n+\n+int\n+bpf_program__load(struct bpf_program *prog)\n+{\n+\tint err;\n+\tstruct bpf_object *obj = prog-\u003eobj;\n+\n+\t/*\n+\t * This function cannot be called before OBJ_PREPARED -\n+\t * Program's data is initialized on object prepare.\n+\t */\n+\tif (obj-\u003estate \u003c OBJ_PREPARED)\n+\t\treturn libbpf_err(-EINVAL);\n+\n+\tif (prog_is_subprog(obj, prog) || prog-\u003eload_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)\n+\t\treturn libbpf_err(-EINVAL);\n+\n+\tif (prog-\u003efd \u003e= 0)\n+\t\treturn libbpf_err(-EBUSY);\n+\n+\terr = bpf_object_load_prog(obj, prog, prog-\u003einsns, prog-\u003einsns_cnt,\n+\t\t\t\t obj-\u003elicense, obj-\u003ekern_version, \u0026prog-\u003efd);\n+\tif (err) {\n+\t\tpr_warn(\"prog '%s': failed to load: %s\\n\", prog-\u003ename, errstr(err));\n+\t\treturn libbpf_err(err);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h\nindex 03f794f4adb03..8dbed8ec37891 100644\n--- a/tools/lib/bpf/libbpf.h\n+++ b/tools/lib/bpf/libbpf.h\n@@ -473,6 +473,17 @@ LIBBPF_API int bpf_program__pin(struct bpf_program *prog, const char *path);\n * @return 0, on success; negative error code, otherwise\n */\n LIBBPF_API int bpf_program__unpin(struct bpf_program *prog, const char *path);\n+\n+/**\n+ * @brief **bpf_program__unload()** unloads a BPF program, closing its fd.\n+ *\n+ * If the program's load strategy is BPF_PROG_LOAD_STRATEGY_MANUAL, only the\n+ * fd is closed and the program's data is retained so it can be reloaded\n+ * later via bpf_program__load(). For any other load strategy, the program's\n+ * data is also freed and it cannot be reloaded.\n+ *\n+ * @param prog BPF program to unload\n+ */\n LIBBPF_API void bpf_program__unload(struct bpf_program *prog);\n \n struct bpf_link;\n@@ -1050,9 +1061,12 @@ LIBBPF_API int bpf_program__add_flags(struct bpf_program *prog, __u32 flags);\n */\n LIBBPF_API int bpf_program__clear_flags(struct bpf_program *prog, __u32 flags);\n \n-/* Per-program log level and log buffer getters/setters.\n+/*\n+ * Per-program log level and log buffer getters/setters.\n * See bpf_object_open_opts comments regarding log_level and log_buf\n- * interactions.\n+ * interactions. The setters return -EBUSY once the object is loaded,\n+ * except for BPF_PROG_LOAD_STRATEGY_MANUAL programs, which are loaded\n+ * independently by bpf_program__load().\n */\n LIBBPF_API __u32 bpf_program__log_level(const struct bpf_program *prog);\n LIBBPF_API int bpf_program__set_log_level(struct bpf_program *prog, __u32 log_level);\n@@ -2117,6 +2131,55 @@ LIBBPF_API int libbpf_unregister_prog_handler(int handler_id);\n */\n LIBBPF_API int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts *opts);\n \n+/**\n+ * The program load strategy:\n+ *\n+ * - BPF_PROG_LOAD_STRATEGY_DISABLED: the program is not loaded.\n+ * - BPF_PROG_LOAD_STRATEGY_AUTO: the program is autoloaded when the bpf_object is loaded.\n+ * - BPF_PROG_LOAD_STRATEGY_MANUAL: the program is loaded and attached manually.\n+ * It is skipped by the skeleton's auto-attach, whatever its autoattach\n+ * setting is.\n+ */\n+enum bpf_prog_load_strategy {\n+\tBPF_PROG_LOAD_STRATEGY_DISABLED = 0,\n+\tBPF_PROG_LOAD_STRATEGY_AUTO,\n+\tBPF_PROG_LOAD_STRATEGY_MANUAL,\n+};\n+\n+/**\n+ * @brief **bpf_program__set_load_strategy()** sets the load strategy of a\n+ * BPF program, controlling whether and when it gets loaded into the kernel.\n+ *\n+ * Can only be called before the enclosing bpf_object is loaded for\n+ * non MANUAL-\u003e non MANUAL transitions. For manual it only\n+ * before bpf_object is prepared.\n+ *\n+ * @param prog BPF program to update\n+ * @param strategy new load strategy for the program\n+ * @return 0 on success; negative error code error.\n+ */\n+LIBBPF_API int bpf_program__set_load_strategy(struct bpf_program *prog,\n+\t\t\t\t\t enum bpf_prog_load_strategy strategy);\n+\n+/**\n+ * @brief **bpf_program__load_strategy()** returns the current load strategy\n+ * of a BPF program.\n+ *\n+ * @param prog BPF program to query\n+ * @return current load strategy of the program\n+ */\n+LIBBPF_API enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program *prog);\n+\n+/**\n+ * @brief **bpf_program__load()** loads a MANUAL BPF program. The enclosing\n+ * bpf_object must already beprepared.\n+ *\n+ * @param prog BPF program to load; non MANUAL programs, already loaded\n+ * programs and subprograms are rejected.\n+ * @return 0 on success; negative error code otherwise\n+ */\n+LIBBPF_API int bpf_program__load(struct bpf_program *prog);\n+\n #ifdef __cplusplus\n } /* extern \"C\" */\n #endif\ndiff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map\nindex 53b7b591325f5..9835e660350b6 100644\n--- a/tools/lib/bpf/libbpf.map\n+++ b/tools/lib/bpf/libbpf.map\n@@ -464,6 +464,9 @@ LIBBPF_1.8.0 {\n \t\tbpf_program__attach_tracing_multi;\n \t\tbpf_program__clear_flags;\n \t\tbpf_program__clone;\n+\t\tbpf_program__load;\n+\t\tbpf_program__load_strategy;\n+\t\tbpf_program__set_load_strategy;\n \t\tbtf__add_loc_param;\n \t\tbtf__add_loc_param_value;\n \t\tbtf__add_loc_proto;\ndiff --git a/tools/testing/selftests/bpf/prog_tests/load_type.c b/tools/testing/selftests/bpf/prog_tests/load_type.c\nnew file mode 100644\nindex 0000000000000..7e344c0b04450\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/load_type.c\n@@ -0,0 +1,154 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003ctest_progs.h\u003e\n+#include \u003ctime.h\u003e\n+#include \"test_load_type.skel.h\"\n+\n+void test_load_type(void)\n+{\n+\tstruct bpf_link *link = NULL;\n+\tstruct test_load_type *skel;\n+\tint err;\n+\n+\tskel = test_load_type__open();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\n+\t/* don't load prog1 */\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_disabled_prog1\"))\n+\t\tgoto cleanup;\n+\n+\t/* load and attach prog2 */\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog2, BPF_PROG_LOAD_STRATEGY_AUTO);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_auto_prog2\"))\n+\t\tgoto cleanup;\n+\tif (!ASSERT_TRUE(bpf_program__autoload(skel-\u003eprogs.prog2), \"prog2_autoload\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_manual\"))\n+\t\tgoto cleanup;\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,\n+\t\t \"prog3_load_strategy\"))\n+\t\tgoto cleanup;\n+\n+\t/*\n+\t * bpf_program__set_autoload() is a thin forwarder to\n+\t * set_load_strategy(), restricted to AUTO/DISABLED to preserve its\n+\t * original bool on/off meaning;\n+\t */\n+\terr = bpf_program__set_autoload(skel-\u003eprogs.prog3, false);\n+\tif (!ASSERT_OK(err, \"set_autoload_false\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog3),\n+\t\t BPF_PROG_LOAD_STRATEGY_DISABLED, \"prog3_load_strategy_after_false\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__set_autoload(skel-\u003eprogs.prog3, true);\n+\tif (!ASSERT_OK(err, \"set_autoload_true\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog3), BPF_PROG_LOAD_STRATEGY_AUTO,\n+\t\t \"prog3_load_strategy_after_true\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_manual_enum\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,\n+\t\t \"prog3_load_strategy_after_manual_enum\"))\n+\t\tgoto cleanup;\n+\n+\t/* changing the load strategy leaves autoattach alone */\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_auto\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog3), BPF_PROG_LOAD_STRATEGY_AUTO,\n+\t\t \"prog3_load_strategy_auto\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_TRUE(bpf_program__autoattach(skel-\u003eprogs.prog3), \"prog3_autoattach_default\"))\n+\t\tgoto cleanup;\n+\n+\tbpf_program__set_autoattach(skel-\u003eprogs.prog3, false);\n+\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_manual_again\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_auto_again\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_FALSE(bpf_program__autoattach(skel-\u003eprogs.prog3), \"prog3_autoattach_kept\"))\n+\t\tgoto cleanup;\n+\n+\t/* restore autoattach to true for the rest of the test */\n+\tbpf_program__set_autoattach(skel-\u003eprogs.prog3, true);\n+\n+\t/* an out-of-range load strategy is rejected */\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, (enum bpf_prog_load_strategy)999);\n+\tif (!ASSERT_EQ(err, -EINVAL, \"set_load_strategy_invalid\"))\n+\t\tgoto cleanup;\n+\n+\t/* change the strategy back to BPF_PROG_LOAD_STRATEGY_MANUAL for the rest of the test */\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_manual_final\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,\n+\t\t \"prog3_load_strategy_final\"))\n+\t\tgoto cleanup;\n+\n+\t/* Test post loaded behaviors */\n+\terr = test_load_type__load(skel);\n+\tif (!ASSERT_OK(err, \"skel_load\"))\n+\t\tgoto cleanup;\n+\n+\t/* Post load we cannot update a program's strategy */\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);\n+\tASSERT_EQ(err, -EINVAL, \"set_load_strategy_after_load\");\n+\n+\terr = test_load_type__attach(skel);\n+\tif (!ASSERT_OK(err, \"skel_attach\"))\n+\t\tgoto cleanup;\n+\n+\tusleep(1);\n+\n+\t/* Post load only AUTOLOAD program is expected to run */\n+\tASSERT_FALSE(skel-\u003ebss-\u003eprog1_called, \"prog1_called\");\n+\tASSERT_TRUE(skel-\u003ebss-\u003eprog2_called, \"prog2_called\");\n+\tASSERT_FALSE(skel-\u003ebss-\u003eprog3_called, \"prog3_called\");\n+\n+\terr = bpf_program__load(skel-\u003eprogs.prog3);\n+\tif (!ASSERT_OK(err, \"load_manually\"))\n+\t\tgoto cleanup;\n+\n+\t/* attach prog3 */\n+\tlink = bpf_program__attach(skel-\u003eprogs.prog3);\n+\tif (!ASSERT_OK_PTR(link, \"attach\"))\n+\t\tgoto cleanup;\n+\n+\tusleep(1);\n+\n+\t/* Post explicit load and attach, MANUAL program is also expected to run */\n+\tif (!ASSERT_TRUE(skel-\u003ebss-\u003eprog3_called, \"prog3_called_again\"))\n+\t\tgoto cleanup;\n+\n+\t/*\n+\t * detach prog3 explicitly: its link is kept in a local variable, not in\n+\t * skel-\u003elinks, so test_load_type__destroy() has nothing to detach\n+\t */\n+\terr = bpf_link__destroy(link);\n+\tASSERT_OK(err, \"link_destroy\");\n+\tlink = NULL;\n+\n+cleanup:\n+\tif (link)\n+\t\tbpf_link__destroy(link);\n+\ttest_load_type__destroy(skel);\n+}\ndiff --git a/tools/testing/selftests/bpf/prog_tests/manual_load.c b/tools/testing/selftests/bpf/prog_tests/manual_load.c\nnew file mode 100644\nindex 0000000000000..e5bdf7e0f04ec\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/manual_load.c\n@@ -0,0 +1,336 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003ctest_progs.h\u003e\n+#include \u003ctime.h\u003e\n+#include \"test_manual_load.skel.h\"\n+\n+#define READ_SZ 456\n+\n+/*\n+ * prog4 is marked SEC(\"!...\") in the source instead of being set\n+ * imperatively; verify that an explicit bpf_program__set_load_strategy() call\n+ * before load overrides the declarative default.\n+ */\n+static void manual_load_verify_override(void)\n+{\n+\tstruct test_manual_load *skel;\n+\tint err;\n+\n+\tskel = test_manual_load__open();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog4, BPF_PROG_LOAD_STRATEGY_DISABLED);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_disabled\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog4),\n+\t\t BPF_PROG_LOAD_STRATEGY_DISABLED, \"prog4_load_strategy_overridden\"))\n+\t\tgoto cleanup;\n+\n+\terr = test_manual_load__load(skel);\n+\tif (!ASSERT_OK(err, \"skel_load\"))\n+\t\tgoto cleanup;\n+\n+\t/*\n+\t * prog4 was overridden to DISABLED, so its load_strategy != MANUAL\n+\t * and load() must reject it\n+\t */\n+\tif (!ASSERT_EQ(bpf_program__fd(skel-\u003eprogs.prog4), -ENOENT, \"prog4_not_loaded\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__load(skel-\u003eprogs.prog4);\n+\tASSERT_EQ(err, -EINVAL, \"load_after_override\");\n+\n+cleanup:\n+\ttest_manual_load__destroy(skel);\n+}\n+\n+/*\n+ * prog4 is MANUAL via its SEC(\"!...\") marker; verify that\n+ * bpf_object__prepare() alone -- without ever calling bpf_object__load() --\n+ * is sufficient for bpf_program__load() to succeed, since BTF\n+ * loading, map creation, and relocation of MANUAL programs are all\n+ * completed by prepare() already.\n+ */\n+static void manual_load_verify_prepare_only(void)\n+{\n+\tstruct test_manual_load *skel;\n+\tstruct bpf_link *link = NULL;\n+\tint err;\n+\n+\tskel = test_manual_load__open();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\n+\terr = bpf_object__prepare(skel-\u003eobj);\n+\tif (!ASSERT_OK(err, \"bpf_object__prepare\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__load(skel-\u003eprogs.prog4);\n+\tif (!ASSERT_OK(err, \"load_after_prepare\"))\n+\t\tgoto cleanup;\n+\n+\tif (!ASSERT_GE(bpf_program__fd(skel-\u003eprogs.prog4), 0, \"prog4_fd_after_prepare\"))\n+\t\tgoto cleanup;\n+\n+\tlink = bpf_program__attach(skel-\u003eprogs.prog4);\n+\tif (!ASSERT_OK_PTR(link, \"attach_after_prepare\"))\n+\t\tgoto cleanup;\n+\n+\tusleep(1);\n+\n+\tif (!ASSERT_TRUE(skel-\u003ebss-\u003eprog4_called, \"prog4_called_after_prepare\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_link__destroy(link);\n+\tlink = NULL;\n+\tif (!ASSERT_OK(err, \"link_destroy_after_prepare\"))\n+\t\tgoto cleanup;\n+\n+\tbpf_program__unload(skel-\u003eprogs.prog4);\n+\tASSERT_LT(bpf_program__fd(skel-\u003eprogs.prog4), 0, \"prog4_fd_closed_after_unload\");\n+\n+cleanup:\n+\tif (link)\n+\t\tbpf_link__destroy(link);\n+\ttest_manual_load__destroy(skel);\n+}\n+\n+/*\n+ * prog5 is disabled at parse time; resolve its attach target against\n+ * module BTF via bpf_program__set_attach_target() before switching it\n+ * to MANUAL and deferring its load past the bulk bpf_object__load().\n+ * Regression test for the module BTF fd/array lifetime.\n+ */\n+static void manual_load_verify_module_btf(void)\n+{\n+\tstruct test_manual_load *skel;\n+\tstruct bpf_link *link;\n+\tint err;\n+\n+\tif (!env.has_testmod) {\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tskel = test_manual_load__open();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\n+\terr = bpf_program__set_attach_target(skel-\u003eprogs.prog5, 0,\n+\t\t\t\t\t \"bpf_testmod:bpf_testmod_test_read\");\n+\tif (!ASSERT_OK(err, \"set_attach_target\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.prog5, BPF_PROG_LOAD_STRATEGY_MANUAL);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_manual\"))\n+\t\tgoto cleanup;\n+\n+\t/* keep the other autoload programs out of the way of this load */\n+\tbpf_program__set_load_strategy(skel-\u003eprogs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);\n+\tbpf_program__set_load_strategy(skel-\u003eprogs.prog2, BPF_PROG_LOAD_STRATEGY_DISABLED);\n+\tbpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);\n+\n+\t/*\n+\t * bulk load: prog5 itself is skipped (MANUAL), but this is where\n+\t * module BTF gets torn down if not correctly deferred\n+\t */\n+\terr = test_manual_load__load(skel);\n+\tif (!ASSERT_OK(err, \"skel_load\"))\n+\t\tgoto cleanup;\n+\n+\t/*\n+\t * deferred load must still succeed: the module BTF fd cached above\n+\t * by set_attach_target() must still be a valid, open fd here\n+\t */\n+\terr = bpf_program__load(skel-\u003eprogs.prog5);\n+\tif (!ASSERT_OK(err, \"load_module_btf\"))\n+\t\tgoto cleanup;\n+\n+\tlink = bpf_program__attach(skel-\u003eprogs.prog5);\n+\tif (!ASSERT_OK_PTR(link, \"attach\"))\n+\t\tgoto cleanup;\n+\n+\tASSERT_OK(trigger_module_test_read(READ_SZ), \"trigger_read\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eprog5_sz, READ_SZ, \"prog5_sz\");\n+\n+\tbpf_link__destroy(link);\n+\n+cleanup:\n+\ttest_manual_load__destroy(skel);\n+}\n+\n+static void manual_load_verify_main_cycle(void)\n+{\n+\tstruct bpf_link *link = NULL;\n+\tstruct test_manual_load *skel;\n+\tint err;\n+\n+\tskel = test_manual_load__open();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\n+\t/*\n+\t * the SEC(\"!...\") prefix alone, with no imperative call, must set\n+\t * prog4's load strategy before it is ever touched below\n+\t */\n+\tif (!ASSERT_EQ(bpf_program__load_strategy(skel-\u003eprogs.prog4),\n+\t\t BPF_PROG_LOAD_STRATEGY_MANUAL, \"prog4_prefix_load_strategy\"))\n+\t\tgoto cleanup;\n+\n+\t/* don't load prog1 */\n+\tbpf_program__set_load_strategy(skel-\u003eprogs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);\n+\n+\t/* prog2 is autoload */\n+\tbpf_program__set_load_strategy(skel-\u003eprogs.prog2, BPF_PROG_LOAD_STRATEGY_AUTO);\n+\n+\t/* prog3 is manually loaded */\n+\tbpf_program__set_load_strategy(skel-\u003eprogs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);\n+\n+\terr = test_manual_load__load(skel);\n+\tif (!ASSERT_OK(err, \"skel_load\"))\n+\t\tgoto cleanup;\n+\n+\terr = test_manual_load__attach(skel);\n+\tif (!ASSERT_OK(err, \"skel_attach\"))\n+\t\tgoto cleanup;\n+\n+\t/* trigger the BPF programs */\n+\tusleep(1);\n+\n+\tASSERT_FALSE(skel-\u003ebss-\u003eprog1_called, \"prog1_called\");\n+\tASSERT_TRUE(skel-\u003ebss-\u003eprog2_called, \"prog2_called\");\n+\tASSERT_FALSE(skel-\u003ebss-\u003eprog3_called, \"prog3_called\");\n+\tASSERT_FALSE(skel-\u003ebss-\u003eprog4_called, \"prog4_called\");\n+\n+\t/* prog1 is disabled for load */\n+\tif (!ASSERT_EQ(bpf_program__fd(skel-\u003eprogs.prog1), -ENOENT, \"prog1_not_loaded\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_program__load(skel-\u003eprogs.prog1);\n+\tif (!ASSERT_EQ(err, -EINVAL, \"load_disabled\"))\n+\t\tgoto cleanup;\n+\n+\t/* prog2 is autoload */\n+\terr = bpf_program__load(skel-\u003eprogs.prog2);\n+\tif (!ASSERT_EQ(err, -EINVAL, \"load_autoload\"))\n+\t\tgoto cleanup;\n+\n+\t/*\n+\t * bpf_program__unload() no longer rejects based on load strategy:\n+\t * calling it on prog2 (AUTO, currently loaded and attached) performs\n+\t * a full, irreversible unload instead of returning an error\n+\t */\n+\tbpf_program__unload(skel-\u003eprogs.prog2);\n+\tASSERT_LT(bpf_program__fd(skel-\u003eprogs.prog2), 0, \"prog2_fd_closed_after_unload\");\n+\n+\t/* load prog3 */\n+\terr = bpf_program__load(skel-\u003eprogs.prog3);\n+\tif (!ASSERT_OK(err, \"load\"))\n+\t\tgoto cleanup;\n+\n+\t/* attach prog3 */\n+\tlink = bpf_program__attach(skel-\u003eprogs.prog3);\n+\tif (!ASSERT_OK_PTR(link, \"attach\"))\n+\t\tgoto cleanup;\n+\n+\tusleep(1);\n+\n+\tif (!ASSERT_TRUE(skel-\u003ebss-\u003eprog3_called, \"prog3_called\"))\n+\t\tgoto cleanup;\n+\n+\t/*\n+\t * detach prog3 explicitly: its link is kept in a local variable, not in\n+\t * skel-\u003elinks, so test_manual_load__destroy() has nothing to detach\n+\t */\n+\terr = bpf_link__destroy(link);\n+\tlink = NULL;\n+\tif (!ASSERT_OK(err, \"link_destroy\"))\n+\t\tgoto cleanup;\n+\n+\t/* reset the call flag after detach */\n+\tskel-\u003ebss-\u003eprog3_called = false;\n+\n+\tusleep(1);\n+\n+\tASSERT_FALSE(skel-\u003ebss-\u003eprog3_called, \"prog3_called\");\n+\n+\t/* unload prog3; MANUAL strategy means its data is retained for reload */\n+\tbpf_program__unload(skel-\u003eprogs.prog3);\n+\n+\t/* reload prog3 */\n+\terr = bpf_program__load(skel-\u003eprogs.prog3);\n+\tif (!ASSERT_OK(err, \"load_reload\"))\n+\t\tgoto cleanup;\n+\n+\t/* reattach prog3 */\n+\tlink = bpf_program__attach(skel-\u003eprogs.prog3);\n+\tif (!ASSERT_OK_PTR(link, \"reattach\"))\n+\t\tgoto cleanup;\n+\n+\tusleep(1);\n+\n+\tif (!ASSERT_TRUE(skel-\u003ebss-\u003eprog3_called, \"prog3_called_reattach\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_link__destroy(link);\n+\tlink = NULL;\n+\tif (!ASSERT_OK(err, \"link_destroy_reattach\"))\n+\t\tgoto cleanup;\n+\n+\t/* reset the call flag after detach */\n+\tskel-\u003ebss-\u003eprog3_called = false;\n+\n+\tusleep(1);\n+\n+\tASSERT_FALSE(skel-\u003ebss-\u003eprog3_called, \"prog3_called\");\n+\n+\t/*\n+\t * run prog4 (declaratively marked) through the same manual\n+\t * load/attach/trigger/detach/unload cycle as prog3\n+\t */\n+\terr = bpf_program__load(skel-\u003eprogs.prog4);\n+\tif (!ASSERT_OK(err, \"prog4_load\"))\n+\t\tgoto cleanup;\n+\n+\tlink = bpf_program__attach(skel-\u003eprogs.prog4);\n+\tif (!ASSERT_OK_PTR(link, \"prog4_attach\"))\n+\t\tgoto cleanup;\n+\n+\tusleep(1);\n+\n+\tif (!ASSERT_TRUE(skel-\u003ebss-\u003eprog4_called, \"prog4_called\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_link__destroy(link);\n+\tlink = NULL;\n+\tif (!ASSERT_OK(err, \"prog4_link_destroy\"))\n+\t\tgoto cleanup;\n+\n+\tbpf_program__unload(skel-\u003eprogs.prog4);\n+\n+\ttest_manual_load__destroy(skel);\n+\treturn;\n+\n+cleanup:\n+\tif (link)\n+\t\tbpf_link__destroy(link);\n+\ttest_manual_load__destroy(skel);\n+}\n+\n+void test_manual_load(void)\n+{\n+\tif (test__start_subtest(\"main_cycle\"))\n+\t\tmanual_load_verify_main_cycle();\n+\n+\tif (test__start_subtest(\"verify_override\"))\n+\t\tmanual_load_verify_override();\n+\n+\tif (test__start_subtest(\"verify_prepare_only\"))\n+\t\tmanual_load_verify_prepare_only();\n+\n+\tif (test__start_subtest(\"verify_module_btf\"))\n+\t\tmanual_load_verify_module_btf();\n+}\n+\ndiff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c\nindex a0f93756e717b..0fbaa2d60aaa2 100644\n--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c\n+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c\n@@ -2168,6 +2168,37 @@ static void signed_module_kfunc_rejected(void)\n \trun_setup(\"cleanup\", dir);\n }\n \n+/*\n+ * gen_loader only records programs loaded by bpf_object__load_progs(), which\n+ * skips MANUAL ones, so an object with a MANUAL program must fail to load\n+ * instead of generating a skeleton whose prog_fd slots no longer line up\n+ * with the object's programs\n+ */\n+static void manual_prog_rejected(void)\n+{\n+\tLIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);\n+\tstruct test_signed_loader *skel;\n+\tint err;\n+\n+\tskel = test_signed_loader__open();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\treturn;\n+\n+\terr = bpf_program__set_load_strategy(skel-\u003eprogs.probe, BPF_PROG_LOAD_STRATEGY_MANUAL);\n+\tif (!ASSERT_OK(err, \"set_load_strategy_manual\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_object__gen_loader(skel-\u003eobj, \u0026gopts);\n+\tif (!ASSERT_OK(err, \"gen_loader\"))\n+\t\tgoto cleanup;\n+\n+\terr = bpf_object__load(skel-\u003eobj);\n+\tASSERT_EQ(err, -EOPNOTSUPP, \"load_rejected\");\n+\n+cleanup:\n+\ttest_signed_loader__destroy(skel);\n+}\n+\n enum subtest_boot {\n \tBOOT_ANY,\n \tBOOT_SEALED,\n@@ -2211,6 +2242,7 @@ static const struct {\n \t{ \"signed_map_by_fd_rejected\", signed_map_by_fd_rejected, BOOT_SEALED },\n \t{ \"signed_sparse_fd_array_rejected\", signed_sparse_fd_array_rejected, BOOT_SEALED },\n \t{ \"bpf_keyring_provisioned\", bpf_keyring_provisioned, BOOT_UNSEALED },\n+\t{ \"manual_prog_rejected\", manual_prog_rejected, BOOT_ANY },\n };\n \n void test_signed_loader(void)\ndiff --git a/tools/testing/selftests/bpf/progs/test_load_type.c b/tools/testing/selftests/bpf/progs/test_load_type.c\nnew file mode 100644\nindex 0000000000000..3d9b81691d7ad\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/test_load_type.c\n@@ -0,0 +1,31 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_helpers.h\u003e\n+\n+bool prog1_called = false;\n+bool prog2_called = false;\n+bool prog3_called = false;\n+\n+SEC(\"raw_tp/sys_enter\")\n+int prog1(const void *ctx)\n+{\n+\tprog1_called = true;\n+\treturn 0;\n+}\n+\n+SEC(\"raw_tp/sys_enter\")\n+int prog2(const void *ctx)\n+{\n+\tprog2_called = true;\n+\treturn 0;\n+}\n+\n+SEC(\"raw_tp/sys_enter\")\n+int prog3(const void *ctx)\n+{\n+\tprog3_called = true;\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/test_manual_load.c b/tools/testing/selftests/bpf/progs/test_manual_load.c\nnew file mode 100644\nindex 0000000000000..6cdbfc21ca379\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/test_manual_load.c\n@@ -0,0 +1,54 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+bool prog1_called = false;\n+bool prog2_called = false;\n+bool prog3_called = false;\n+bool prog4_called = false;\n+__u32 prog5_sz = 0;\n+\n+SEC(\"raw_tp/sys_enter\")\n+int prog1(const void *ctx)\n+{\n+\tprog1_called = true;\n+\treturn 0;\n+}\n+\n+SEC(\"raw_tp/sys_enter\")\n+int prog2(const void *ctx)\n+{\n+\tprog2_called = true;\n+\treturn 0;\n+}\n+\n+SEC(\"raw_tp/sys_enter\")\n+int prog3(const void *ctx)\n+{\n+\tprog3_called = true;\n+\treturn 0;\n+}\n+\n+SEC(\"!raw_tp/sys_enter\")\n+int prog4(const void *ctx)\n+{\n+\tprog4_called = true;\n+\treturn 0;\n+}\n+\n+/*\n+ * disabled at parse time; its attach target is resolved against module\n+ * BTF via bpf_program__set_attach_target() before it is switched to\n+ * MANUAL and loaded\n+ */\n+SEC(\"?fentry\")\n+int BPF_PROG(prog5, struct file *file, struct kobject *kobj,\n+\t const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)\n+{\n+\tprog5_sz = len;\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c\nindex 9cfc9b4b41c12..0cd19b59037f6 100644\n--- a/tools/testing/selftests/bpf/veristat.c\n+++ b/tools/testing/selftests/bpf/veristat.c\n@@ -1678,7 +1678,6 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf\n \tconst char *base_filename = basename(strdupa(filename));\n \tconst char *prog_name = bpf_program__name(prog);\n \tlong mem_peak_a, mem_peak_b, mem_peak = -1;\n-\tLIBBPF_OPTS(bpf_prog_load_opts, opts);\n \tchar *buf;\n \tint buf_sz, log_level;\n \tstruct verif_stats *stats;\n@@ -1726,18 +1725,15 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf\n \tif (env.force_reg_invariants)\n \t\tbpf_program__add_flags(prog, BPF_F_TEST_REG_INVARIANTS);\n \n-\topts.log_buf = buf;\n-\topts.log_size = buf_sz;\n-\topts.log_level = log_level;\n+\tbpf_program__set_log_buf(prog, buf, buf_sz);\n+\tbpf_program__set_log_level(prog, log_level);\n \n \tcgroup_err = reset_stat_cgroup();\n \tmem_peak_a = cgroup_memory_peak();\n-\tfd = bpf_program__clone(prog, \u0026opts);\n-\tif (fd \u003c 0) {\n-\t\terr = fd;\n-\t\tif (env.verbose)\n-\t\t\tfprintf(stderr, \"Failed to load program %s %d\\n\", prog_name, err);\n-\t}\n+\terr = bpf_program__load(prog);\n+\tfd = err == 0 ? bpf_program__fd(prog) : -1;\n+\tif (err \u0026\u0026 env.verbose)\n+\t\tfprintf(stderr, \"Failed to load program %s %d\\n\", prog_name, err);\n \tmem_peak_b = cgroup_memory_peak();\n \tif (!cgroup_err \u0026\u0026 mem_peak_a \u003e= 0 \u0026\u0026 mem_peak_b \u003e= 0)\n \t\tmem_peak = mem_peak_b - mem_peak_a;\n@@ -1774,7 +1770,7 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf\n \tif (verif_log_buf != buf)\n \t\tfree(buf);\n \tif (fd \u003e 0)\n-\t\tclose(fd);\n+\t\tbpf_program__unload(prog);\n \treturn 0;\n }\n \n@@ -2297,7 +2293,7 @@ static int process_obj(const char *filename)\n \tenv.files_processed++;\n \n \tbpf_object__for_each_program(prog, obj) {\n-\t\tbpf_program__set_autoload(prog, true);\n+\t\tbpf_program__set_load_strategy(prog, BPF_PROG_LOAD_STRATEGY_MANUAL);\n \t\tprog_cnt++;\n \t}\n \n"
} |
| 2/1 | 2026/10/10 15:05 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "All modified files are located within tools/ (specifically tools/lib/bpf/ and tools/testing/selftests/bpf/), affecting only user-space libbpf and selftests/veristat. They do not alter the compiled Linux kernel image (vmlinux) or kernel modules, and thus are not reachable or suitable for kernel fuzzing with syzkaller.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit f6db0d7a9b8584feca495a8d8f8b169d53252434
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Oct 10 15:05:53 2026 +0000
syz-cluster: applied patch under review
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index cb09ded907732..043f220ebb136 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -494,7 +494,7 @@ struct bpf_program {
struct bpf_object *obj;
int fd;
- bool autoload;
+ enum bpf_prog_load_strategy load_strategy;
bool autoattach;
bool sym_global;
/* the program or a function that it calls has callx */
@@ -758,6 +758,7 @@ struct bpf_object {
bool has_subcalls;
bool has_rodata;
+ bool has_manual_progs;
struct bpf_gen *gen_loader;
@@ -864,7 +865,7 @@ static Elf_Data *elf_sec_data(const struct bpf_object *obj, Elf_Scn *scn);
static Elf64_Sym *elf_sym_by_idx(const struct bpf_object *obj, size_t idx);
static Elf64_Rel *elf_rel_by_idx(Elf_Data *data, size_t idx);
-void bpf_program__unload(struct bpf_program *prog)
+static void bpf_program_unload_full(struct bpf_program *prog)
{
if (!prog)
return;
@@ -876,12 +877,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;
+ }
+
+ bpf_program_unload_full(prog);
+}
+
static void bpf_program__exit(struct bpf_program *prog)
{
if (!prog)
return;
- bpf_program__unload(prog);
+ bpf_program_unload_full(prog);
zfree(&prog->name);
zfree(&prog->sec_name);
zfree(&prog->insns);
@@ -949,13 +968,21 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
/* libbpf's convention for SEC("?abc...") is that it's just like
* SEC("abc...") but the corresponding bpf_program starts out with
* autoload set to false.
+ *
+ * Similarly, SEC("!abc...") marks the program for manual loading:
+ * it is skipped by the bulk auto-load pass and must be explicitly
+ * loaded via bpf_program__load().
*/
if (sec_name[0] == '?') {
- prog->autoload = false;
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
/* from now on forget there was ? in section name */
sec_name++;
+ } else if (sec_name[0] == '!') {
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
+ /* from now on forget there was ! in section name */
+ sec_name++;
} else {
- prog->autoload = true;
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_AUTO;
}
prog->autoattach = true;
@@ -1222,6 +1249,15 @@ static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
continue;
+ /*
+ * We avoid this for MANUAL programs to allow veristat
+ * conversion from clone() to MANUAL loading. Setting AUTO
+ * for a program would cause it to be rejected during manual
+ * loading.
+ */
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
+ continue;
+
for (j = 0; j < obj->nr_maps; ++j) {
const struct btf_type *type;
@@ -1242,7 +1278,8 @@ static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)
}
}
if (use_cnt)
- prog->autoload = should_load;
+ prog->load_strategy = should_load ? BPF_PROG_LOAD_STRATEGY_AUTO
+ : BPF_PROG_LOAD_STRATEGY_DISABLED;
}
return 0;
@@ -1333,7 +1370,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
* then bpf_object_adjust_struct_ops_autoload() will update its
* autoload accordingly.
*/
- st_ops->progs[i]->autoload = false;
+ st_ops->progs[i]->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
st_ops->progs[i] = NULL;
}
@@ -1371,7 +1408,7 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
* if user replaced it with another program or NULL
*/
if (st_ops->progs[i] && st_ops->progs[i] != prog)
- st_ops->progs[i]->autoload = false;
+ st_ops->progs[i]->load_strategy = BPF_PROG_LOAD_STRATEGY_DISABLED;
/* Update the value from the shadow type */
st_ops->progs[i] = prog;
@@ -3806,7 +3843,7 @@ static bool obj_needs_vmlinux_btf(const struct bpf_object *obj)
}
bpf_object__for_each_program(prog, obj) {
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
if (prog_needs_vmlinux_btf(prog))
return true;
@@ -5867,6 +5904,20 @@ static int init_prog_array_slots(struct bpf_object *obj, struct bpf_map *map)
continue;
targ_prog = map->init_slots[i];
+
+ /*
+ * Be careful about manual progs, reject if they weren't loaded before
+ * this step to not insert invalid FDs into the map, otherwise we will
+ * get a much less clear rejection from the kernel on the element update
+ * below.
+ */
+ if (targ_prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL &&
+ targ_prog->fd < 0) {
+ pr_warn("map '%s': slot [%u] prog '%s' is MANUAL but not loaded\n",
+ map->name, i, targ_prog->name);
+ return -EINVAL;
+ }
+
fd = bpf_program__fd(targ_prog);
err = bpf_map_update_elem(map->fd, &i, &fd, 0);
@@ -6495,7 +6546,7 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
/* no need to apply CO-RE relocation if the program is
* not going to be loaded
*/
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
/* adjust insn_idx from section frame of reference to the local
@@ -8166,7 +8217,7 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat
*/
if (prog_is_subprog(obj, prog))
continue;
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
err = bpf_object__relocate_calls(obj, prog);
@@ -8202,7 +8253,7 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat
prog = &obj->programs[i];
if (prog_is_subprog(obj, prog))
continue;
- if (!prog->autoload)
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_DISABLED)
continue;
/* Process data relos for main programs */
@@ -9241,8 +9292,8 @@ bpf_object__load_progs(struct bpf_object *obj, int log_level)
prog = &obj->programs[i];
if (prog_is_subprog(obj, prog))
continue;
- if (!prog->autoload) {
- pr_debug("prog '%s': skipped loading\n", prog->name);
+ if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO) {
+ pr_debug("prog '%s': skipped auto-loading\n", prog->name);
continue;
}
prog->log_level |= log_level;
@@ -9270,6 +9321,8 @@ static int bpf_object_prepare_progs(struct bpf_object *obj)
for (i = 0; i < obj->nr_programs; i++) {
prog = &obj->programs[i];
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL)
+ obj->has_manual_progs = true;
err = bpf_object__sanitize_prog(obj, prog);
if (err)
return err;
@@ -9296,6 +9349,21 @@ static int bpf_object_init_progs(struct bpf_object *obj, const struct bpf_object
prog->type = prog->sec_def->prog_type;
prog->expected_attach_type = prog->sec_def->expected_attach_type;
+ /*
+ * struct_ops programs declared SEC("!...") are rejected
+ * here at declarative parse time for manual loading.
+ * bpf_program__set_load_strategy() does allow MANUAL for struct_ops
+ * via its imperative API, but only as a narrow accommodation for
+ * veristat, which needs to verify each program in isolation
+ * without ever calling bpf_object__load().
+ */
+ if (prog->type == BPF_PROG_TYPE_STRUCT_OPS &&
+ prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
+ pr_warn("prog '%s': struct_ops programs do not support manual loading\n",
+ prog->name);
+ return -EINVAL;
+ }
+
/* sec_def can have custom callback which should be called
* after bpf_program is initialized to adjust its properties
*/
@@ -9496,7 +9564,7 @@ static int bpf_object_unload(struct bpf_object *obj)
}
for (i = 0; i < obj->nr_programs; i++)
- bpf_program__unload(&obj->programs[i]);
+ bpf_program_unload_full(&obj->programs[i]);
return 0;
}
@@ -9887,7 +9955,7 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
return 0;
}
-static void bpf_map_prepare_vdata(const struct bpf_map *map)
+static int bpf_map_prepare_vdata(const struct bpf_map *map)
{
const struct btf_type *type;
struct bpf_struct_ops *st_ops;
@@ -9903,16 +9971,32 @@ static void bpf_map_prepare_vdata(const struct bpf_map *map)
if (!prog)
continue;
+ /*
+ * Manual struct_ops programs don't get along well with manual
+ * loading: all the progs belonging to the same struct_ops must
+ * load and unload together, which is the opposite of the manual
+ * load concept.
+ * Still, we allow manual progs here as long as they were loaded
+ * before this step ast this still will work.
+ */
+ if (prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL && prog->fd < 0) {
+ pr_warn("map '%s': struct_ops member prog '%s' is MANUAL but not loaded\n",
+ map->name, prog->name);
+ return -EINVAL;
+ }
+
prog_fd = bpf_program__fd(prog);
kern_data = st_ops->kern_vdata + st_ops->kern_func_off[i];
*(unsigned long *)kern_data = prog_fd;
}
+
+ return 0;
}
static int bpf_object_prepare_struct_ops(struct bpf_object *obj)
{
struct bpf_map *map;
- int i;
+ int i, err;
for (i = 0; i < obj->nr_maps; i++) {
map = &obj->maps[i];
@@ -9923,7 +10007,9 @@ static int bpf_object_prepare_struct_ops(struct bpf_object *obj)
if (!map->autocreate)
continue;
- bpf_map_prepare_vdata(map);
+ err = bpf_map_prepare_vdata(map);
+ if (err)
+ return err;
}
return 0;
@@ -9939,10 +10025,19 @@ static void bpf_object_unpin(struct bpf_object *obj)
bpf_map__unpin(&obj->maps[i], NULL);
}
-static void bpf_object_cleanup_btf(struct bpf_object *obj)
+static void bpf_object_cleanup_btf(struct bpf_object *obj, bool force)
{
int i;
+ /*
+ * Module BTF fds and btf_vmlinux are still needed by
+ * programs that have not been manually loaded yet, so defer freeing
+ * them to the end of the object lifetime, unless the caller forces
+ * immediate cleanup.
+ */
+ if (!force && obj->has_manual_progs)
+ return;
+
/* clean up module BTFs */
for (i = 0; i < obj->btf_module_cnt; i++) {
close(obj->btf_modules[i].fd);
@@ -9959,13 +10054,38 @@ static void bpf_object_cleanup_btf(struct bpf_object *obj)
obj->btf_vmlinux = NULL;
}
-static void bpf_object_post_load_cleanup(struct bpf_object *obj)
+static void bpf_object_post_load_cleanup(struct bpf_object *obj, bool force)
{
- /* clean up fd_array */
- zfree(&obj->fd_array);
+ /*
+ * The fd array is needed for manually loaded programs,
+ * so defer freeing it in that case to the end of the object lifetime.
+ */
+ if (force || !obj->has_manual_progs)
+ zfree(&obj->fd_array);
/* clean up BTF */
- bpf_object_cleanup_btf(obj);
+ bpf_object_cleanup_btf(obj, force);
+}
+
+static int bpf_object_check_gen_loader(struct bpf_object *obj)
+{
+ struct bpf_program *prog;
+
+ if (!obj->gen_loader)
+ return 0;
+
+ /*
+ * gen_loader only records programs loaded by bpf_object__load_progs(),
+ * which skips MANUAL ones.
+ */
+ bpf_object__for_each_program(prog, obj) {
+ if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
+ continue;
+ pr_warn("prog '%s': gen_loader does not support manually-loaded programs\n",
+ prog->name);
+ return -EOPNOTSUPP;
+ }
+ return 0;
}
static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_path)
@@ -9977,7 +10097,8 @@ static int bpf_object_prepare(struct bpf_object *obj, const char *target_btf_pat
return -EINVAL;
}
- err = bpf_object_prepare_token(obj);
+ err = bpf_object_check_gen_loader(obj);
+ err = err ? : bpf_object_prepare_token(obj);
err = err ? : bpf_object__probe_loading(obj);
err = err ? : bpf_object__load_vmlinux_btf(obj, false);
err = err ? : bpf_object__resolve_externs(obj, obj->kconfig);
@@ -10032,7 +10153,8 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch
prog_has_callx(&obj->programs[i]))
text_has_callx = true;
for (i = 0; i < obj->nr_programs; i++)
- if (obj->programs[i].autoload && !prog_is_subprog(obj, &obj->programs[i]) &&
+ if (obj->programs[i].load_strategy != BPF_PROG_LOAD_STRATEGY_DISABLED &&
+ !prog_is_subprog(obj, &obj->programs[i]) &&
(text_has_callx || prog_has_callx(&obj->programs[i])))
nr_progs++;
bpf_gen__init(obj->gen_loader, obj->log_level | extra_log_level,
@@ -10059,7 +10181,7 @@ static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch
err = bpf_gen__finish(obj->gen_loader, obj->nr_programs, obj->nr_maps);
}
- bpf_object_post_load_cleanup(obj);
+ bpf_object_post_load_cleanup(obj, false);
obj->state = OBJ_LOADED; /* doesn't matter if successfully or not */
if (err) {
@@ -10527,7 +10649,7 @@ void bpf_object__close(struct bpf_object *obj)
* bpf_object__load(), we need to clean up stuff that is normally
* cleaned up at the end of loading step
*/
- bpf_object_post_load_cleanup(obj);
+ bpf_object_post_load_cleanup(obj, true);
usdt_manager_free(obj->usdt_man);
obj->usdt_man = NULL;
@@ -10536,7 +10658,6 @@ void bpf_object__close(struct bpf_object *obj)
bpf_object__elf_finish(obj);
bpf_object_unload(obj);
btf__free(obj->btf);
- btf__free(obj->btf_vmlinux);
btf_ext__free(obj->btf_ext);
for (i = 0; i < obj->nr_maps; i++)
@@ -10637,6 +10758,9 @@ int bpf_object__gen_loader(struct bpf_object *obj, struct gen_loader_opts *opts)
return libbpf_err(-EFAULT);
if (!OPTS_VALID(opts, gen_loader_opts))
return libbpf_err(-EINVAL);
+ if (obj->state >= OBJ_PREPARED)
+ return libbpf_err(-EINVAL);
+
gen = calloc(1, sizeof(*gen));
if (!gen)
return libbpf_err(-ENOMEM);
@@ -10713,16 +10837,13 @@ const char *bpf_program__section_name(const struct bpf_program *prog)
bool bpf_program__autoload(const struct bpf_program *prog)
{
- return prog->autoload;
+ return prog->load_strategy == BPF_PROG_LOAD_STRATEGY_AUTO;
}
int bpf_program__set_autoload(struct bpf_program *prog, bool autoload)
{
- if (prog->obj->state >= OBJ_LOADED)
- return libbpf_err(-EINVAL);
-
- prog->autoload = autoload;
- return 0;
+ return bpf_program__set_load_strategy(prog,
+ autoload ? BPF_PROG_LOAD_STRATEGY_AUTO : BPF_PROG_LOAD_STRATEGY_DISABLED);
}
bool bpf_program__autoattach(const struct bpf_program *prog)
@@ -10862,7 +10983,13 @@ __u32 bpf_program__log_level(const struct bpf_program *prog)
int bpf_program__set_log_level(struct bpf_program *prog, __u32 log_level)
{
- if (prog->obj->state >= OBJ_LOADED)
+ /*
+ * Here and below, OBJ_LOADED is not a relevant restriction for
+ * manual programs as those can load after the bpf object is
+ * already loaded.
+ */
+ if (prog->obj->state >= OBJ_LOADED &&
+ prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
return libbpf_err(-EBUSY);
prog->log_level = log_level;
@@ -10881,7 +11008,8 @@ int bpf_program__set_log_buf(struct bpf_program *prog, char *log_buf, size_t log
return libbpf_err(-EINVAL);
if (log_size > UINT_MAX)
return libbpf_err(-EINVAL);
- if (prog->obj->state >= OBJ_LOADED)
+ if (prog->obj->state >= OBJ_LOADED &&
+ prog->load_strategy != BPF_PROG_LOAD_STRATEGY_MANUAL)
return libbpf_err(-EBUSY);
prog->log_buf = log_buf;
@@ -13661,7 +13789,7 @@ static int collect_func_ids_by_glob(const struct bpf_program *prog, const char *
err = collect_btf_func_ids_by_glob(btf, pattern, ids);
cleanup:
- bpf_object_cleanup_btf(obj);
+ bpf_object_cleanup_btf(obj, false);
return err;
}
@@ -16105,7 +16233,7 @@ int bpf_object__attach_skeleton(struct bpf_object_skeleton *s)
struct bpf_program *prog = *prog_skel->prog;
struct bpf_link **link = prog_skel->link;
- if (!prog->autoload || !prog->autoattach)
+ if (prog->load_strategy != BPF_PROG_LOAD_STRATEGY_AUTO || !prog->autoattach)
continue;
/* auto-attaching not supported for this program */
@@ -16215,3 +16343,77 @@ void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s)
free(s->progs);
free(s);
}
+
+int bpf_program__set_load_strategy(struct bpf_program *prog, enum bpf_prog_load_strategy strategy)
+{
+ struct bpf_object *obj = prog->obj;
+
+ /*
+ * obj->has_manual_prog is set once in bpf_object_prepare_progs()
+ * and never recomputed; once the object is prepared, no transition
+ * into or out of MANUAL may change which programs are MANUAL,
+ * regardless of direction. AUTO<->DISABLED transitions never touch
+ * MANUAL and keep the looser, pre-existing OBJ_LOADED gate.
+ */
+ if (strategy == BPF_PROG_LOAD_STRATEGY_MANUAL ||
+ prog->load_strategy == BPF_PROG_LOAD_STRATEGY_MANUAL) {
+ if (obj->state >= OBJ_PREPARED)
+ return libbpf_err(-EINVAL);
+ } else if (obj->state >= OBJ_LOADED) {
+ return libbpf_err(-EINVAL);
+ }
+
+ if (strategy == prog->load_strategy)
+ return 0;
+
+ switch (strategy) {
+ case BPF_PROG_LOAD_STRATEGY_DISABLED:
+ case BPF_PROG_LOAD_STRATEGY_AUTO:
+ prog->load_strategy = strategy;
+ break;
+ case BPF_PROG_LOAD_STRATEGY_MANUAL:
+ if (prog_is_subprog(obj, prog))
+ return libbpf_err(-EINVAL);
+
+ prog->load_strategy = BPF_PROG_LOAD_STRATEGY_MANUAL;
+ break;
+ default:
+ return libbpf_err(-EINVAL);
+ }
+
+ return 0;
+}
+
+enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program *prog)
+{
+ 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);
+
+ 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);
+ }
+
+ return 0;
+}
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 03f794f4adb03..8dbed8ec37891 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -473,6 +473,17 @@ LIBBPF_API int bpf_program__pin(struct bpf_program *prog, const char *path);
* @return 0, on success; negative error code, otherwise
*/
LIBBPF_API int bpf_program__unpin(struct bpf_program *prog, const char *path);
+
+/**
+ * @brief **bpf_program__unload()** unloads a BPF program, closing its fd.
+ *
+ * If the program's load strategy is BPF_PROG_LOAD_STRATEGY_MANUAL, only the
+ * fd is closed and the program's data is retained so it can be reloaded
+ * later via bpf_program__load(). For any other load strategy, the program's
+ * data is also freed and it cannot be reloaded.
+ *
+ * @param prog BPF program to unload
+ */
LIBBPF_API void bpf_program__unload(struct bpf_program *prog);
struct bpf_link;
@@ -1050,9 +1061,12 @@ LIBBPF_API int bpf_program__add_flags(struct bpf_program *prog, __u32 flags);
*/
LIBBPF_API int bpf_program__clear_flags(struct bpf_program *prog, __u32 flags);
-/* Per-program log level and log buffer getters/setters.
+/*
+ * Per-program log level and log buffer getters/setters.
* See bpf_object_open_opts comments regarding log_level and log_buf
- * interactions.
+ * interactions. The setters return -EBUSY once the object is loaded,
+ * except for BPF_PROG_LOAD_STRATEGY_MANUAL programs, which are loaded
+ * independently by bpf_program__load().
*/
LIBBPF_API __u32 bpf_program__log_level(const struct bpf_program *prog);
LIBBPF_API int bpf_program__set_log_level(struct bpf_program *prog, __u32 log_level);
@@ -2117,6 +2131,55 @@ LIBBPF_API int libbpf_unregister_prog_handler(int handler_id);
*/
LIBBPF_API int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts *opts);
+/**
+ * The program load strategy:
+ *
+ * - BPF_PROG_LOAD_STRATEGY_DISABLED: the program is not loaded.
+ * - BPF_PROG_LOAD_STRATEGY_AUTO: the program is autoloaded when the bpf_object is loaded.
+ * - BPF_PROG_LOAD_STRATEGY_MANUAL: the program is loaded and attached manually.
+ * It is skipped by the skeleton's auto-attach, whatever its autoattach
+ * setting is.
+ */
+enum bpf_prog_load_strategy {
+ BPF_PROG_LOAD_STRATEGY_DISABLED = 0,
+ BPF_PROG_LOAD_STRATEGY_AUTO,
+ BPF_PROG_LOAD_STRATEGY_MANUAL,
+};
+
+/**
+ * @brief **bpf_program__set_load_strategy()** sets the load strategy of a
+ * BPF program, controlling whether and when it gets loaded into the kernel.
+ *
+ * Can only be called before the enclosing bpf_object is loaded for
+ * non MANUAL-> non MANUAL transitions. For manual it only
+ * before bpf_object is prepared.
+ *
+ * @param prog BPF program to update
+ * @param strategy new load strategy for the program
+ * @return 0 on success; negative error code error.
+ */
+LIBBPF_API int bpf_program__set_load_strategy(struct bpf_program *prog,
+ enum bpf_prog_load_strategy strategy);
+
+/**
+ * @brief **bpf_program__load_strategy()** returns the current load strategy
+ * of a BPF program.
+ *
+ * @param prog BPF program to query
+ * @return current load strategy of the program
+ */
+LIBBPF_API enum bpf_prog_load_strategy bpf_program__load_strategy(const struct bpf_program *prog);
+
+/**
+ * @brief **bpf_program__load()** loads a MANUAL BPF program. The enclosing
+ * bpf_object must already beprepared.
+ *
+ * @param prog BPF program to load; non MANUAL programs, already loaded
+ * programs and subprograms are rejected.
+ * @return 0 on success; negative error code otherwise
+ */
+LIBBPF_API int bpf_program__load(struct bpf_program *prog);
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 53b7b591325f5..9835e660350b6 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -464,6 +464,9 @@ LIBBPF_1.8.0 {
bpf_program__attach_tracing_multi;
bpf_program__clear_flags;
bpf_program__clone;
+ bpf_program__load;
+ bpf_program__load_strategy;
+ bpf_program__set_load_strategy;
btf__add_loc_param;
btf__add_loc_param_value;
btf__add_loc_proto;
diff --git a/tools/testing/selftests/bpf/prog_tests/load_type.c b/tools/testing/selftests/bpf/prog_tests/load_type.c
new file mode 100644
index 0000000000000..7e344c0b04450
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/load_type.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <time.h>
+#include "test_load_type.skel.h"
+
+void test_load_type(void)
+{
+ struct bpf_link *link = NULL;
+ struct test_load_type *skel;
+ int err;
+
+ skel = test_load_type__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ /* don't load prog1 */
+ err = bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ if (!ASSERT_OK(err, "set_load_strategy_disabled_prog1"))
+ goto cleanup;
+
+ /* load and attach prog2 */
+ err = bpf_program__set_load_strategy(skel->progs.prog2, BPF_PROG_LOAD_STRATEGY_AUTO);
+ if (!ASSERT_OK(err, "set_load_strategy_auto_prog2"))
+ goto cleanup;
+ if (!ASSERT_TRUE(bpf_program__autoload(skel->progs.prog2), "prog2_autoload"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual"))
+ goto cleanup;
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,
+ "prog3_load_strategy"))
+ goto cleanup;
+
+ /*
+ * bpf_program__set_autoload() is a thin forwarder to
+ * set_load_strategy(), restricted to AUTO/DISABLED to preserve its
+ * original bool on/off meaning;
+ */
+ err = bpf_program__set_autoload(skel->progs.prog3, false);
+ if (!ASSERT_OK(err, "set_autoload_false"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3),
+ BPF_PROG_LOAD_STRATEGY_DISABLED, "prog3_load_strategy_after_false"))
+ goto cleanup;
+
+ err = bpf_program__set_autoload(skel->progs.prog3, true);
+ if (!ASSERT_OK(err, "set_autoload_true"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_AUTO,
+ "prog3_load_strategy_after_true"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual_enum"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,
+ "prog3_load_strategy_after_manual_enum"))
+ goto cleanup;
+
+ /* changing the load strategy leaves autoattach alone */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);
+ if (!ASSERT_OK(err, "set_load_strategy_auto"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_AUTO,
+ "prog3_load_strategy_auto"))
+ goto cleanup;
+
+ if (!ASSERT_TRUE(bpf_program__autoattach(skel->progs.prog3), "prog3_autoattach_default"))
+ goto cleanup;
+
+ bpf_program__set_autoattach(skel->progs.prog3, false);
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual_again"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_AUTO);
+ if (!ASSERT_OK(err, "set_load_strategy_auto_again"))
+ goto cleanup;
+
+ if (!ASSERT_FALSE(bpf_program__autoattach(skel->progs.prog3), "prog3_autoattach_kept"))
+ goto cleanup;
+
+ /* restore autoattach to true for the rest of the test */
+ bpf_program__set_autoattach(skel->progs.prog3, true);
+
+ /* an out-of-range load strategy is rejected */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, (enum bpf_prog_load_strategy)999);
+ if (!ASSERT_EQ(err, -EINVAL, "set_load_strategy_invalid"))
+ goto cleanup;
+
+ /* change the strategy back to BPF_PROG_LOAD_STRATEGY_MANUAL for the rest of the test */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual_final"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog3), BPF_PROG_LOAD_STRATEGY_MANUAL,
+ "prog3_load_strategy_final"))
+ goto cleanup;
+
+ /* Test post loaded behaviors */
+ err = test_load_type__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ /* Post load we cannot update a program's strategy */
+ err = bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ ASSERT_EQ(err, -EINVAL, "set_load_strategy_after_load");
+
+ err = test_load_type__attach(skel);
+ if (!ASSERT_OK(err, "skel_attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ /* Post load only AUTOLOAD program is expected to run */
+ ASSERT_FALSE(skel->bss->prog1_called, "prog1_called");
+ ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+
+ err = bpf_program__load(skel->progs.prog3);
+ if (!ASSERT_OK(err, "load_manually"))
+ goto cleanup;
+
+ /* attach prog3 */
+ link = bpf_program__attach(skel->progs.prog3);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ /* Post explicit load and attach, MANUAL program is also expected to run */
+ if (!ASSERT_TRUE(skel->bss->prog3_called, "prog3_called_again"))
+ goto cleanup;
+
+ /*
+ * detach prog3 explicitly: its link is kept in a local variable, not in
+ * skel->links, so test_load_type__destroy() has nothing to detach
+ */
+ err = bpf_link__destroy(link);
+ ASSERT_OK(err, "link_destroy");
+ link = NULL;
+
+cleanup:
+ if (link)
+ bpf_link__destroy(link);
+ test_load_type__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/manual_load.c b/tools/testing/selftests/bpf/prog_tests/manual_load.c
new file mode 100644
index 0000000000000..e5bdf7e0f04ec
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/manual_load.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <time.h>
+#include "test_manual_load.skel.h"
+
+#define READ_SZ 456
+
+/*
+ * prog4 is marked SEC("!...") in the source instead of being set
+ * imperatively; verify that an explicit bpf_program__set_load_strategy() call
+ * before load overrides the declarative default.
+ */
+static void manual_load_verify_override(void)
+{
+ struct test_manual_load *skel;
+ int err;
+
+ skel = test_manual_load__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog4, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ if (!ASSERT_OK(err, "set_load_strategy_disabled"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog4),
+ BPF_PROG_LOAD_STRATEGY_DISABLED, "prog4_load_strategy_overridden"))
+ goto cleanup;
+
+ err = test_manual_load__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ /*
+ * prog4 was overridden to DISABLED, so its load_strategy != MANUAL
+ * and load() must reject it
+ */
+ if (!ASSERT_EQ(bpf_program__fd(skel->progs.prog4), -ENOENT, "prog4_not_loaded"))
+ goto cleanup;
+
+ err = bpf_program__load(skel->progs.prog4);
+ ASSERT_EQ(err, -EINVAL, "load_after_override");
+
+cleanup:
+ test_manual_load__destroy(skel);
+}
+
+/*
+ * prog4 is MANUAL via its SEC("!...") marker; verify that
+ * bpf_object__prepare() alone -- without ever calling bpf_object__load() --
+ * is sufficient for bpf_program__load() to succeed, since BTF
+ * loading, map creation, and relocation of MANUAL programs are all
+ * completed by prepare() already.
+ */
+static void manual_load_verify_prepare_only(void)
+{
+ struct test_manual_load *skel;
+ struct bpf_link *link = NULL;
+ int err;
+
+ skel = test_manual_load__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_object__prepare(skel->obj);
+ if (!ASSERT_OK(err, "bpf_object__prepare"))
+ goto cleanup;
+
+ err = bpf_program__load(skel->progs.prog4);
+ if (!ASSERT_OK(err, "load_after_prepare"))
+ goto cleanup;
+
+ if (!ASSERT_GE(bpf_program__fd(skel->progs.prog4), 0, "prog4_fd_after_prepare"))
+ goto cleanup;
+
+ link = bpf_program__attach(skel->progs.prog4);
+ if (!ASSERT_OK_PTR(link, "attach_after_prepare"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog4_called, "prog4_called_after_prepare"))
+ goto cleanup;
+
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "link_destroy_after_prepare"))
+ goto cleanup;
+
+ bpf_program__unload(skel->progs.prog4);
+ ASSERT_LT(bpf_program__fd(skel->progs.prog4), 0, "prog4_fd_closed_after_unload");
+
+cleanup:
+ if (link)
+ bpf_link__destroy(link);
+ test_manual_load__destroy(skel);
+}
+
+/*
+ * prog5 is disabled at parse time; resolve its attach target against
+ * module BTF via bpf_program__set_attach_target() before switching it
+ * to MANUAL and deferring its load past the bulk bpf_object__load().
+ * Regression test for the module BTF fd/array lifetime.
+ */
+static void manual_load_verify_module_btf(void)
+{
+ struct test_manual_load *skel;
+ struct bpf_link *link;
+ int err;
+
+ if (!env.has_testmod) {
+ test__skip();
+ return;
+ }
+
+ skel = test_manual_load__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_program__set_attach_target(skel->progs.prog5, 0,
+ "bpf_testmod:bpf_testmod_test_read");
+ if (!ASSERT_OK(err, "set_attach_target"))
+ goto cleanup;
+
+ err = bpf_program__set_load_strategy(skel->progs.prog5, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual"))
+ goto cleanup;
+
+ /* keep the other autoload programs out of the way of this load */
+ bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ bpf_program__set_load_strategy(skel->progs.prog2, BPF_PROG_LOAD_STRATEGY_DISABLED);
+ bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_DISABLED);
+
+ /*
+ * bulk load: prog5 itself is skipped (MANUAL), but this is where
+ * module BTF gets torn down if not correctly deferred
+ */
+ err = test_manual_load__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ /*
+ * deferred load must still succeed: the module BTF fd cached above
+ * by set_attach_target() must still be a valid, open fd here
+ */
+ err = bpf_program__load(skel->progs.prog5);
+ if (!ASSERT_OK(err, "load_module_btf"))
+ goto cleanup;
+
+ link = bpf_program__attach(skel->progs.prog5);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ goto cleanup;
+
+ ASSERT_OK(trigger_module_test_read(READ_SZ), "trigger_read");
+ ASSERT_EQ(skel->bss->prog5_sz, READ_SZ, "prog5_sz");
+
+ bpf_link__destroy(link);
+
+cleanup:
+ test_manual_load__destroy(skel);
+}
+
+static void manual_load_verify_main_cycle(void)
+{
+ struct bpf_link *link = NULL;
+ struct test_manual_load *skel;
+ int err;
+
+ skel = test_manual_load__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ /*
+ * the SEC("!...") prefix alone, with no imperative call, must set
+ * prog4's load strategy before it is ever touched below
+ */
+ if (!ASSERT_EQ(bpf_program__load_strategy(skel->progs.prog4),
+ BPF_PROG_LOAD_STRATEGY_MANUAL, "prog4_prefix_load_strategy"))
+ goto cleanup;
+
+ /* don't load prog1 */
+ bpf_program__set_load_strategy(skel->progs.prog1, BPF_PROG_LOAD_STRATEGY_DISABLED);
+
+ /* prog2 is autoload */
+ bpf_program__set_load_strategy(skel->progs.prog2, BPF_PROG_LOAD_STRATEGY_AUTO);
+
+ /* prog3 is manually loaded */
+ bpf_program__set_load_strategy(skel->progs.prog3, BPF_PROG_LOAD_STRATEGY_MANUAL);
+
+ err = test_manual_load__load(skel);
+ if (!ASSERT_OK(err, "skel_load"))
+ goto cleanup;
+
+ err = test_manual_load__attach(skel);
+ if (!ASSERT_OK(err, "skel_attach"))
+ goto cleanup;
+
+ /* trigger the BPF programs */
+ usleep(1);
+
+ ASSERT_FALSE(skel->bss->prog1_called, "prog1_called");
+ ASSERT_TRUE(skel->bss->prog2_called, "prog2_called");
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+ ASSERT_FALSE(skel->bss->prog4_called, "prog4_called");
+
+ /* prog1 is disabled for load */
+ if (!ASSERT_EQ(bpf_program__fd(skel->progs.prog1), -ENOENT, "prog1_not_loaded"))
+ goto cleanup;
+
+ err = bpf_program__load(skel->progs.prog1);
+ if (!ASSERT_EQ(err, -EINVAL, "load_disabled"))
+ goto cleanup;
+
+ /* prog2 is autoload */
+ err = bpf_program__load(skel->progs.prog2);
+ if (!ASSERT_EQ(err, -EINVAL, "load_autoload"))
+ goto cleanup;
+
+ /*
+ * bpf_program__unload() no longer rejects based on load strategy:
+ * calling it on prog2 (AUTO, currently loaded and attached) performs
+ * a full, irreversible unload instead of returning an error
+ */
+ bpf_program__unload(skel->progs.prog2);
+ ASSERT_LT(bpf_program__fd(skel->progs.prog2), 0, "prog2_fd_closed_after_unload");
+
+ /* load prog3 */
+ err = bpf_program__load(skel->progs.prog3);
+ if (!ASSERT_OK(err, "load"))
+ goto cleanup;
+
+ /* attach prog3 */
+ link = bpf_program__attach(skel->progs.prog3);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog3_called, "prog3_called"))
+ goto cleanup;
+
+ /*
+ * detach prog3 explicitly: its link is kept in a local variable, not in
+ * skel->links, so test_manual_load__destroy() has nothing to detach
+ */
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "link_destroy"))
+ goto cleanup;
+
+ /* reset the call flag after detach */
+ skel->bss->prog3_called = false;
+
+ usleep(1);
+
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+
+ /* unload prog3; MANUAL strategy means its data is retained for reload */
+ bpf_program__unload(skel->progs.prog3);
+
+ /* reload prog3 */
+ err = bpf_program__load(skel->progs.prog3);
+ if (!ASSERT_OK(err, "load_reload"))
+ goto cleanup;
+
+ /* reattach prog3 */
+ link = bpf_program__attach(skel->progs.prog3);
+ if (!ASSERT_OK_PTR(link, "reattach"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog3_called, "prog3_called_reattach"))
+ goto cleanup;
+
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "link_destroy_reattach"))
+ goto cleanup;
+
+ /* reset the call flag after detach */
+ skel->bss->prog3_called = false;
+
+ usleep(1);
+
+ ASSERT_FALSE(skel->bss->prog3_called, "prog3_called");
+
+ /*
+ * run prog4 (declaratively marked) through the same manual
+ * load/attach/trigger/detach/unload cycle as prog3
+ */
+ err = bpf_program__load(skel->progs.prog4);
+ if (!ASSERT_OK(err, "prog4_load"))
+ goto cleanup;
+
+ link = bpf_program__attach(skel->progs.prog4);
+ if (!ASSERT_OK_PTR(link, "prog4_attach"))
+ goto cleanup;
+
+ usleep(1);
+
+ if (!ASSERT_TRUE(skel->bss->prog4_called, "prog4_called"))
+ goto cleanup;
+
+ err = bpf_link__destroy(link);
+ link = NULL;
+ if (!ASSERT_OK(err, "prog4_link_destroy"))
+ goto cleanup;
+
+ bpf_program__unload(skel->progs.prog4);
+
+ test_manual_load__destroy(skel);
+ return;
+
+cleanup:
+ if (link)
+ bpf_link__destroy(link);
+ test_manual_load__destroy(skel);
+}
+
+void test_manual_load(void)
+{
+ if (test__start_subtest("main_cycle"))
+ manual_load_verify_main_cycle();
+
+ if (test__start_subtest("verify_override"))
+ manual_load_verify_override();
+
+ if (test__start_subtest("verify_prepare_only"))
+ manual_load_verify_prepare_only();
+
+ if (test__start_subtest("verify_module_btf"))
+ manual_load_verify_module_btf();
+}
+
diff --git a/tools/testing/selftests/bpf/prog_tests/signed_loader.c b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
index a0f93756e717b..0fbaa2d60aaa2 100644
--- a/tools/testing/selftests/bpf/prog_tests/signed_loader.c
+++ b/tools/testing/selftests/bpf/prog_tests/signed_loader.c
@@ -2168,6 +2168,37 @@ static void signed_module_kfunc_rejected(void)
run_setup("cleanup", dir);
}
+/*
+ * gen_loader only records programs loaded by bpf_object__load_progs(), which
+ * skips MANUAL ones, so an object with a MANUAL program must fail to load
+ * instead of generating a skeleton whose prog_fd slots no longer line up
+ * with the object's programs
+ */
+static void manual_prog_rejected(void)
+{
+ LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
+ struct test_signed_loader *skel;
+ int err;
+
+ skel = test_signed_loader__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return;
+
+ err = bpf_program__set_load_strategy(skel->progs.probe, BPF_PROG_LOAD_STRATEGY_MANUAL);
+ if (!ASSERT_OK(err, "set_load_strategy_manual"))
+ goto cleanup;
+
+ err = bpf_object__gen_loader(skel->obj, &gopts);
+ if (!ASSERT_OK(err, "gen_loader"))
+ goto cleanup;
+
+ err = bpf_object__load(skel->obj);
+ ASSERT_EQ(err, -EOPNOTSUPP, "load_rejected");
+
+cleanup:
+ test_signed_loader__destroy(skel);
+}
+
enum subtest_boot {
BOOT_ANY,
BOOT_SEALED,
@@ -2211,6 +2242,7 @@ static const struct {
{ "signed_map_by_fd_rejected", signed_map_by_fd_rejected, BOOT_SEALED },
{ "signed_sparse_fd_array_rejected", signed_sparse_fd_array_rejected, BOOT_SEALED },
{ "bpf_keyring_provisioned", bpf_keyring_provisioned, BOOT_UNSEALED },
+ { "manual_prog_rejected", manual_prog_rejected, BOOT_ANY },
};
void test_signed_loader(void)
diff --git a/tools/testing/selftests/bpf/progs/test_load_type.c b/tools/testing/selftests/bpf/progs/test_load_type.c
new file mode 100644
index 0000000000000..3d9b81691d7ad
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_load_type.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+bool prog1_called = false;
+bool prog2_called = false;
+bool prog3_called = false;
+
+SEC("raw_tp/sys_enter")
+int prog1(const void *ctx)
+{
+ prog1_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog2(const void *ctx)
+{
+ prog2_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog3(const void *ctx)
+{
+ prog3_called = true;
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_manual_load.c b/tools/testing/selftests/bpf/progs/test_manual_load.c
new file mode 100644
index 0000000000000..6cdbfc21ca379
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_manual_load.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+bool prog1_called = false;
+bool prog2_called = false;
+bool prog3_called = false;
+bool prog4_called = false;
+__u32 prog5_sz = 0;
+
+SEC("raw_tp/sys_enter")
+int prog1(const void *ctx)
+{
+ prog1_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog2(const void *ctx)
+{
+ prog2_called = true;
+ return 0;
+}
+
+SEC("raw_tp/sys_enter")
+int prog3(const void *ctx)
+{
+ prog3_called = true;
+ return 0;
+}
+
+SEC("!raw_tp/sys_enter")
+int prog4(const void *ctx)
+{
+ prog4_called = true;
+ return 0;
+}
+
+/*
+ * disabled at parse time; its attach target is resolved against module
+ * BTF via bpf_program__set_attach_target() before it is switched to
+ * MANUAL and loaded
+ */
+SEC("?fentry")
+int BPF_PROG(prog5, struct file *file, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)
+{
+ prog5_sz = len;
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c
index 9cfc9b4b41c12..0cd19b59037f6 100644
--- a/tools/testing/selftests/bpf/veristat.c
+++ b/tools/testing/selftests/bpf/veristat.c
@@ -1678,7 +1678,6 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
const char *base_filename = basename(strdupa(filename));
const char *prog_name = bpf_program__name(prog);
long mem_peak_a, mem_peak_b, mem_peak = -1;
- LIBBPF_OPTS(bpf_prog_load_opts, opts);
char *buf;
int buf_sz, log_level;
struct verif_stats *stats;
@@ -1726,18 +1725,15 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
if (env.force_reg_invariants)
bpf_program__add_flags(prog, BPF_F_TEST_REG_INVARIANTS);
- opts.log_buf = buf;
- opts.log_size = buf_sz;
- opts.log_level = log_level;
+ bpf_program__set_log_buf(prog, buf, buf_sz);
+ bpf_program__set_log_level(prog, log_level);
cgroup_err = reset_stat_cgroup();
mem_peak_a = cgroup_memory_peak();
- fd = bpf_program__clone(prog, &opts);
- if (fd < 0) {
- err = fd;
- if (env.verbose)
- fprintf(stderr, "Failed to load program %s %d\n", prog_name, err);
- }
+ err = bpf_program__load(prog);
+ fd = err == 0 ? bpf_program__fd(prog) : -1;
+ if (err && env.verbose)
+ fprintf(stderr, "Failed to load program %s %d\n", prog_name, err);
mem_peak_b = cgroup_memory_peak();
if (!cgroup_err && mem_peak_a >= 0 && mem_peak_b >= 0)
mem_peak = mem_peak_b - mem_peak_a;
@@ -1774,7 +1770,7 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
if (verif_log_buf != buf)
free(buf);
if (fd > 0)
- close(fd);
+ bpf_program__unload(prog);
return 0;
}
@@ -2297,7 +2293,7 @@ static int process_obj(const char *filename)
env.files_processed++;
bpf_object__for_each_program(prog, obj) {
- bpf_program__set_autoload(prog, true);
+ bpf_program__set_load_strategy(prog, BPF_PROG_LOAD_STRATEGY_MANUAL);
prog_cnt++;
}
|
| 3/2 | 2026/10/10 15:05 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20326 output: 17 thoughts: 500 Thoughts: **Fuzzing Evaluation: A Preliminary Assessment** Okay, so I need to decide if this patch series warrants syzkaller fuzzing. My immediate thought is to start by examining the modified files. Let's see... the diff touches: `tools/lib/bpf/libbpf.c`, `tools/lib/bpf/libbpf.h`, `tools/lib/bpf/libbpf.map`, `tools/testing/selftests/bpf/prog_tests/load_type.c`, `tools/testing/selftests/bpf/prog_tests/manual_load.c`, `tools/testing/selftests/bpf/prog_tests/signed_loader.c`, `tools/testing/selftests/bpf/progs/test_load_type.c`, `tools/testing/selftests/bpf/progs/test_manual_load.c`, and `tools/testing/selftests/bpf/veristat.c`. Hmm, every single file is within the `tools/` directory. That's interesting. Now, let's break this down. We have `tools/lib/bpf/...`, which is clearly the user-space libbpf library. Then, `tools/testing/selftests/...` covers the selftests and the `veristat` user-space test tool. Time to apply my expert understanding. According to my negative criteria, I *should* return `WorthFuzzing=false` ONLY if all changes fall strictly into one or more of these categories. The negative criteria are clear about this: I need to consider changes which are non-kernel and non-executable. User-space directories like `tools/`, self-tests, samples, or scripts that do not affect the compiled kernel image are exempt, which seems to apply here. But, before I jump to a conclusion, I need to make *absolutely* sure. Just to be completely rigorous, I'll quickly check the git diff against HEAD to confirm that no other files outside the `tools/` directory have been modified. Better safe than sorry. |
| 4/2 | 2026/10/10 15:06 | tool | grepper |
0mArgs:{
"Expression": "diff --git"
}Results: {
"Output": "Documentation/bpf/drgn.rst=99=For example, to print ``struct bpf_prog_aux`` for BPF program id 53077:\n--\nDocumentation/bpf/drgn.rst-103- % git diff\nDocumentation/bpf/drgn.rst:104: diff --git a/tools/bpf_inspect.py b/tools/bpf_inspect.py\nDocumentation/bpf/drgn.rst-105- index 650e228..aea2357 100755\n--\nDocumentation/dev-tools/checkuapi.rst=54=won't break userspace::\n--\nDocumentation/dev-tools/checkuapi.rst-69- * comp2_t is 24-bit with 5-bit base 2 exponent and 20 bit fraction\nDocumentation/dev-tools/checkuapi.rst:70: diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h\nDocumentation/dev-tools/checkuapi.rst-71- EOF\n--\nDocumentation/dev-tools/checkuapi.rst=310=Consider this change::\n--\nDocumentation/dev-tools/checkuapi.rst-312- cat \u003c\u003c 'EOF' | patch -l -p1\nDocumentation/dev-tools/checkuapi.rst:313: diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-generic/Kbuild\nDocumentation/dev-tools/checkuapi.rst-314- index ebb180aac74e..a9c88b0a8b3b 100644\n--\nDocumentation/doc-guide/contributing.rst=84=looked like this::\n--\nDocumentation/doc-guide/contributing.rst-102-\nDocumentation/doc-guide/contributing.rst:103: diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c\nDocumentation/doc-guide/contributing.rst-104- index 57f6944d65a6..00c9b80b3d33 100644\n--\nDocumentation/mm/transhuge.rst=73=change::\nDocumentation/mm/transhuge.rst-74-\nDocumentation/mm/transhuge.rst:75:\tdiff --git a/mm/mremap.c b/mm/mremap.c\nDocumentation/mm/transhuge.rst-76-\t--- a/mm/mremap.c\n--\nDocumentation/translations/it_IT/doc-guide/contributing.rst=91=patch risultante era questa::\n--\nDocumentation/translations/it_IT/doc-guide/contributing.rst-109-\nDocumentation/translations/it_IT/doc-guide/contributing.rst:110: diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c\nDocumentation/translations/it_IT/doc-guide/contributing.rst-111- index 57f6944d65a6..00c9b80b3d33 100644\n--\nDocumentation/translations/zh_CN/doc-guide/contributing.rst=36=C代码编译器发出的警告常常会被视为误报,从而导致出现了旨在让编译器闭嘴的补丁。\n--\nDocumentation/translations/zh_CN/doc-guide/contributing.rst-86-\nDocumentation/translations/zh_CN/doc-guide/contributing.rst:87: diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c\nDocumentation/translations/zh_CN/doc-guide/contributing.rst-88- index 57f6944d65a6..00c9b80b3d33 100644\n--\nscripts/checkpatch.pl=2671=sub process {\n--\nscripts/checkpatch.pl-2844-\t\t ($line =~ /^rename (?:from|to) \\S+\\s*$/ ||\nscripts/checkpatch.pl:2845:\t\t $line =~ /^diff --git a\\/[\\w\\/\\.\\_\\-]+ b\\/\\S+\\s*$/))) {\nscripts/checkpatch.pl-2846-\t\t\t$is_patch = 1;\n--\nscripts/checkpatch.pl-2902-\t\t# extract the filename as it passes\nscripts/checkpatch.pl:2903:\t\tif ($line =~ /^diff --git.*?(\\S+)$/) {\nscripts/checkpatch.pl-2904-\t\t\t$realfile = $1;\n--\nscripts/get_maintainer.pl=552=foreach my $file (@ARGV) {\n--\nscripts/get_maintainer.pl-601-\t\tpush(@files, $filename);\nscripts/get_maintainer.pl:602:\t } elsif (m/^diff --git a\\/(\\S+) b\\/(\\S+)\\s*$/) {\nscripts/get_maintainer.pl-603-\t\tmy $filename1 = $1;\n"
} |
| 5/2 | 2026/10/10 15:06 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22590 output: 111 thoughts: 771 Thoughts: **Analysis of the Patch for Fuzzing Suitability** Okay, let's break this down. First, I need to confirm what files were actually touched in this `HEAD` commit. The `diff` output is clear: we're looking at changes across several files within the `tools/lib/bpf/` and `tools/testing/selftests/bpf/` directories. Now, I need to assess the significance of these changes for fuzzing. Crucially, none of these files appear to be part of the core kernel image (`vmlinux`) or any loadable kernel modules. They seem to be purely user-space components: the `libbpf` library and various selftests. This aligns directly with the established criteria. The documentation specifies that modifications to user-space directories, self-tests, samples, or scripts that *don't* impact the kernel image or modules should result in a `WorthFuzzing=false` assessment. Let's double-check the rules. It says `WorthFuzzing=false` *only* if the changes fall *exclusively* into a set of non-kernel categories, including user-space directories, self-tests, and scripts. That's exactly what we see here. All the files in this commit are related to user-space libraries and tests. Therefore, applying the provided instructions is straightforward. Based on these files, `WorthFuzzing` is unequivocally `false`. Consequently, `FocusSymbols` should be an empty list, and so should `EnableConfigs`. The reasoning is concise: the changes are limited to user-space tools and tests, so there's no need to enable fuzzing for the core kernel. Time to call the `set-results` function. |
| 6/2 | 2026/10/10 15:07 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "All modified files are located within tools/ (specifically tools/lib/bpf/ and tools/testing/selftests/bpf/), affecting only user-space libbpf and selftests/veristat. They do not alter the compiled Linux kernel image (vmlinux) or kernel modules, and thus are not reachable or suitable for kernel fuzzing with syzkaller.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "All modified files are located within tools/ (specifically tools/lib/bpf/ and tools/testing/selftests/bpf/), affecting only user-space libbpf and selftests/veristat. They do not alter the compiled Linux kernel image (vmlinux) or kernel modules, and thus are not reachable or suitable for kernel fuzzing with syzkaller.",
"WorthFuzzing": false
} |
| 7/1 | 2026/10/10 15:07 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|