AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/15 16:16 flow patch-triage
46m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies only user-space code within tools/lib/bpf (libbpf library) and tools/testing/selftests/bpf (selftests). It does not alter the Linux kernel image or any kernel modules, so it is not applicable for syzkaller kernel fuzzing.",
  "WorthFuzzing": false
}

1/1 2026/09/15 16:16 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 1c26e8c142f46fd710419e0db8830de92cd26a96\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Sep 15 16:16:04 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 c036e8a91ed88..a77115e1b1715 100644\n--- a/tools/lib/bpf/libbpf.c\n+++ b/tools/lib/bpf/libbpf.c\n@@ -751,6 +751,8 @@ struct bpf_object {\n \tbool btf_modules_loaded;\n \tsize_t btf_module_cnt;\n \tsize_t btf_module_cap;\n+\tchar **btf_module_allowlist;\n+\tssize_t btf_module_allowlist_cnt;\n \n \t/* optional log settings passed to BPF_BTF_LOAD and BPF_PROG_LOAD commands */\n \tchar *log_buf;\n@@ -5851,6 +5853,21 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,\n \treturn 0;\n }\n \n+static bool is_btf_mod_allowed(const struct bpf_object *obj, const char *name)\n+{\n+\tssize_t i;\n+\n+\tif (obj-\u003ebtf_module_allowlist_cnt \u003c 0)\n+\t\treturn true;\n+\n+\tfor (i = 0; i \u003c obj-\u003ebtf_module_allowlist_cnt; i++) {\n+\t\tif (strcmp(obj-\u003ebtf_module_allowlist[i], name) == 0)\n+\t\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n static int load_module_btfs(struct bpf_object *obj)\n {\n \tstruct bpf_btf_info info;\n@@ -5873,6 +5890,9 @@ static int load_module_btfs(struct bpf_object *obj)\n \tif (!kernel_supports(obj, FEAT_MODULE_BTF))\n \t\treturn 0;\n \n+\tif (obj-\u003ebtf_module_allowlist_cnt == 0)\n+\t\treturn 0;\n+\n \twhile (true) {\n \t\terr = bpf_btf_get_next_id(id, \u0026id);\n \t\tif (err \u0026\u0026 errno == ENOENT)\n@@ -5915,6 +5935,11 @@ static int load_module_btfs(struct bpf_object *obj)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!is_btf_mod_allowed(obj, name)) {\n+\t\t\tclose(fd);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tbtf = btf_get_from_fd(fd, obj-\u003ebtf_vmlinux);\n \t\terr = libbpf_get_error(btf);\n \t\tif (err) {\n@@ -5939,6 +5964,9 @@ static int load_module_btfs(struct bpf_object *obj)\n \t\t\tbreak;\n \t\t}\n \t\tobj-\u003ebtf_module_cnt++;\n+\n+\t\tif (obj-\u003ebtf_module_allowlist_cnt == obj-\u003ebtf_module_cnt)\n+\t\t\tbreak;\n \t}\n \n \tif (err) {\n@@ -8481,6 +8509,8 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,\n \t\t\t\t\t  const struct bpf_object_open_opts *opts)\n {\n \tconst char *kconfig, *btf_tmp_path, *token_path;\n+\tsize_t mod_allow_cnt, i, j;\n+\tconst char **mod_allow;\n \tstruct bpf_object *obj;\n \tint err;\n \tchar *log_buf;\n@@ -8525,6 +8555,22 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,\n \tif (token_path \u0026\u0026 strlen(token_path) \u003e= PATH_MAX)\n \t\treturn ERR_PTR(-ENAMETOOLONG);\n \n+\tmod_allow = OPTS_GET(opts, btf_module_allowlist, NULL);\n+\tmod_allow_cnt = OPTS_GET(opts, btf_module_allowlist_cnt, 0);\n+\n+\tif (mod_allow_cnt \u003e SSIZE_MAX || (!mod_allow \u0026\u0026 mod_allow_cnt \u003e 0))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tfor (i = 0; i \u003c mod_allow_cnt; i++) {\n+\t\tif (!mod_allow[i] || !mod_allow[i][0])\n+\t\t\treturn ERR_PTR(-EINVAL);\n+\n+\t\tfor (j = 0; j \u003c i; j++) {\n+\t\t\tif (strcmp(mod_allow[i], mod_allow[j]) == 0)\n+\t\t\t\treturn ERR_PTR(-EINVAL);\n+\t\t}\n+\t}\n+\n \tobj = bpf_object__new(path, obj_buf, obj_buf_sz, obj_name);\n \tif (IS_ERR(obj))\n \t\treturn obj;\n@@ -8563,6 +8609,24 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,\n \t\t}\n \t}\n \n+\tobj-\u003ebtf_module_allowlist_cnt = mod_allow ? mod_allow_cnt : -1;\n+\tif (mod_allow_cnt \u003e 0) {\n+\t\tobj-\u003ebtf_module_allowlist =\n+\t\t\tcalloc(mod_allow_cnt, sizeof(*obj-\u003ebtf_module_allowlist));\n+\t\tif (!obj-\u003ebtf_module_allowlist) {\n+\t\t\terr = -ENOMEM;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i \u003c mod_allow_cnt; i++) {\n+\t\t\tobj-\u003ebtf_module_allowlist[i] = strdup(mod_allow[i]);\n+\t\t\tif (!obj-\u003ebtf_module_allowlist[i]) {\n+\t\t\t\terr = -ENOMEM;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \terr = bpf_object__elf_init(obj);\n \terr = err ? : bpf_object__elf_collect(obj);\n \terr = err ? : bpf_object__collect_externs(obj);\n@@ -9684,6 +9748,12 @@ void bpf_object__close(struct bpf_object *obj)\n \t\tclose(obj-\u003ejumptable_maps[i].fd);\n \tzfree(\u0026obj-\u003ejumptable_maps);\n \n+\tif (obj-\u003ebtf_module_allowlist) {\n+\t\tfor (i = 0; i \u003c obj-\u003ebtf_module_allowlist_cnt; i++)\n+\t\t\tzfree(\u0026obj-\u003ebtf_module_allowlist[i]);\n+\t\tzfree(\u0026obj-\u003ebtf_module_allowlist);\n+\t}\n+\n \tfree(obj);\n }\n \ndiff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h\nindex b965ad5715403..89768244d5d9e 100644\n--- a/tools/lib/bpf/libbpf.h\n+++ b/tools/lib/bpf/libbpf.h\n@@ -224,10 +224,24 @@ struct bpf_object_open_opts {\n \t * point (/sys/fs/bpf), in case this default behavior is undesirable.\n \t */\n \tconst char *bpf_token_path;\n+\t/*\n+\t * Optional allowlist of kernel module names whose BTFs libbpf is\n+\t * allowed to load. The allowlist limits which kernel module BTFs libbpf\n+\t * will consult wherever module BTF might be needed.\n+\t *\n+\t * When the option is not specified, the existing behavior remains\n+\t * unchanged. An explicitly specified empty list prevents libbpf from\n+\t * consulting any kernel module BTFs.\n+\t *\n+\t * The list must contain valid, non-empty module names and must not\n+\t * contain duplicate entries; otherwise -EINVAL is returned.\n+\t */\n+\tconst char **btf_module_allowlist;\n+\tsize_t btf_module_allowlist_cnt;\n \n \tsize_t :0;\n };\n-#define bpf_object_open_opts__last_field bpf_token_path\n+#define bpf_object_open_opts__last_field btf_module_allowlist_cnt\n \n /**\n  * @brief **bpf_object__open()** creates a bpf_object by opening\ndiff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c\nnew file mode 100644\nindex 0000000000000..3189133ee58b6\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c\n@@ -0,0 +1,162 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003ctest_progs.h\u003e\n+#include \u003cbpf/btf.h\u003e\n+#include \"btf_module_allowlist.skel.h\"\n+\n+static void test_load_with_list(const char **list, size_t count,\n+\t\t\t\tint expected_ret)\n+{\n+\tstruct btf_module_allowlist *skel;\n+\tint ret;\n+\tLIBBPF_OPTS(bpf_object_open_opts, opts,\n+\t\t.btf_module_allowlist = list,\n+\t\t.btf_module_allowlist_cnt = count,\n+\t);\n+\n+\tskel = btf_module_allowlist__open_opts(\u0026opts);\n+\tif (!ASSERT_OK_PTR(skel, \"btf_module_allowlist__open_opts\"))\n+\t\treturn;\n+\n+\tret = btf_module_allowlist__load(skel);\n+\tASSERT_EQ(ret, expected_ret, \"btf_module_allowlist__load\");\n+\n+\tbtf_module_allowlist__destroy(skel);\n+}\n+\n+static void btf_module_allowlist_default(void)\n+{\n+\tstruct btf_module_allowlist *skel;\n+\tint ret;\n+\n+\tskel = btf_module_allowlist__open();\n+\tif (!ASSERT_OK_PTR(skel, \"btf_module_allowlist__open\"))\n+\t\treturn;\n+\n+\tret = btf_module_allowlist__load(skel);\n+\tASSERT_OK(ret, \"btf_module_allowlist__load\");\n+\n+\tbtf_module_allowlist__destroy(skel);\n+}\n+\n+static void btf_module_allowlist_allow(void)\n+{\n+\tconst char *mod_list[] = { \"bpf_testmod\" };\n+\n+\ttest_load_with_list(mod_list, 1, 0);\n+}\n+\n+/*\n+ * A non-NULL allowlist with a count of 0 explicitly disables module BTFs.\n+ * The pointer must be non-NULL to distinguish this from an unspecified\n+ * allowlist.\n+ */\n+static void btf_module_allowlist_emptylist(void)\n+{\n+\tconst char *mod_list[] = { NULL };\n+\n+\ttest_load_with_list(mod_list, 0, -ESRCH);\n+}\n+\n+/*\n+ * bpf_testmod is not in the allowlist, so its BTF should not be loaded.\n+ */\n+static void btf_module_allowlist_skipunlisted(void)\n+{\n+\tconst char *mod_list[] = { \"module_nonexist\" };\n+\tstruct btf_module_allowlist *skel;\n+\tint ret;\n+\tLIBBPF_OPTS(bpf_object_open_opts, opts,\n+\t\t.btf_module_allowlist = mod_list,\n+\t\t.btf_module_allowlist_cnt = 1,\n+\t);\n+\n+\tskel = btf_module_allowlist__open_opts(\u0026opts);\n+\tif (!ASSERT_OK_PTR(skel, \"btf_module_allowlist__open_opts\"))\n+\t\treturn;\n+\n+\tret = bpf_program__set_attach_target(skel-\u003eprogs.test_btf_module_allowlist, 0,\n+\t\t\t\t\t     \"bpf_testmod:bpf_testmod_fentry_test1\");\n+\tASSERT_EQ(ret, -ESRCH, \"bpf_program__set_attach_target\");\n+\tbtf_module_allowlist__destroy(skel);\n+}\n+\n+static void test_invalid_input(const char **list, size_t count,\n+\t\t\t       const char *test_name)\n+{\n+\tstruct btf_module_allowlist *skel;\n+\tchar assert_name[64];\n+\tint err;\n+\tLIBBPF_OPTS(bpf_object_open_opts, opts,\n+\t\t.btf_module_allowlist = list,\n+\t\t.btf_module_allowlist_cnt = count,\n+\t);\n+\n+\tsnprintf(assert_name, sizeof(assert_name), \"%s: open_opts\", test_name);\n+\tskel = btf_module_allowlist__open_opts(\u0026opts);\n+\terr = errno;\n+\tif (!ASSERT_NULL(skel, assert_name)) {\n+\t\tbtf_module_allowlist__destroy(skel);\n+\t\treturn;\n+\t}\n+\tsnprintf(assert_name, sizeof(assert_name), \"%s: open_opts err\", test_name);\n+\tASSERT_EQ(err, EINVAL, assert_name);\n+}\n+\n+static void btf_module_allowlist_invalidinput(void)\n+{\n+\tconst char *names[] = { NULL };\n+\tconst char *empty_names[] = { \"\" };\n+\tconst char *duplicate_names[] = { \"bpf_testmod\", \"bpf_testmod\" };\n+\n+\ttest_invalid_input(NULL, 1, \"null_list\");\n+\ttest_invalid_input(names, 1, \"null_name\");\n+\ttest_invalid_input(empty_names, 1, \"empty_name\");\n+\ttest_invalid_input(duplicate_names, 2, \"duplicate_name\");\n+\ttest_invalid_input(names, (size_t)SSIZE_MAX + 1, \"large_count\");\n+}\n+\n+void test_btf_module_allowlist(void)\n+{\n+\tstruct btf *vmlinux_btf = NULL;\n+\tstruct btf *module_btf = NULL;\n+\n+\tif (!env.has_testmod) {\n+\t\tprintf(\"%s:SKIP: bpf_testmod is not available\\n\", __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tvmlinux_btf = btf__load_vmlinux_btf();\n+\tif (!vmlinux_btf) {\n+\t\tprintf(\"%s:SKIP: vmlinux_btf is not available\\n\", __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\t/* Ensure bpf_testmod BTF is available. */\n+\tmodule_btf = btf__load_module_btf(\"bpf_testmod\", vmlinux_btf);\n+\tif (!module_btf) {\n+\t\tprintf(\"%s:SKIP: bpf_testmod's BTF is not available\\n\", __func__);\n+\t\tbtf__free(vmlinux_btf);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tbtf__free(module_btf);\n+\tbtf__free(vmlinux_btf);\n+\n+\tif (test__start_subtest(\"btf_module_allowlist_default\"))\n+\t\tbtf_module_allowlist_default();\n+\n+\tif (test__start_subtest(\"btf_module_allowlist_allow\"))\n+\t\tbtf_module_allowlist_allow();\n+\n+\tif (test__start_subtest(\"btf_module_allowlist_skipunlisted\"))\n+\t\tbtf_module_allowlist_skipunlisted();\n+\n+\tif (test__start_subtest(\"btf_module_allowlist_emptylist\"))\n+\t\tbtf_module_allowlist_emptylist();\n+\n+\tif (test__start_subtest(\"btf_module_allowlist_invalidinput\"))\n+\t\tbtf_module_allowlist_invalidinput();\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/btf_module_allowlist.c b/tools/testing/selftests/bpf/progs/btf_module_allowlist.c\nnew file mode 100644\nindex 0000000000000..bb3c38bee77d6\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/btf_module_allowlist.c\n@@ -0,0 +1,13 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+\n+SEC(\"fentry/bpf_testmod_loop_test\")\n+int BPF_PROG(test_btf_module_allowlist)\n+{\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n"
}

2/1 2026/09/15 16:16 agent patch-evaluator
46m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies only user-space code within tools/lib/bpf (libbpf library) and tools/testing/selftests/bpf (selftests). It does not alter the Linux kernel image or any kernel modules, so it is not applicable for syzkaller kernel fuzzing.",
  "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 1c26e8c142f46fd710419e0db8830de92cd26a96
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 15 16:16:04 2026 +0000

    syz-cluster: applied patch under review

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index c036e8a91ed88..a77115e1b1715 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -751,6 +751,8 @@ struct bpf_object {
 	bool btf_modules_loaded;
 	size_t btf_module_cnt;
 	size_t btf_module_cap;
+	char **btf_module_allowlist;
+	ssize_t btf_module_allowlist_cnt;
 
 	/* optional log settings passed to BPF_BTF_LOAD and BPF_PROG_LOAD commands */
 	char *log_buf;
@@ -5851,6 +5853,21 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
 	return 0;
 }
 
+static bool is_btf_mod_allowed(const struct bpf_object *obj, const char *name)
+{
+	ssize_t i;
+
+	if (obj->btf_module_allowlist_cnt < 0)
+		return true;
+
+	for (i = 0; i < obj->btf_module_allowlist_cnt; i++) {
+		if (strcmp(obj->btf_module_allowlist[i], name) == 0)
+			return true;
+	}
+
+	return false;
+}
+
 static int load_module_btfs(struct bpf_object *obj)
 {
 	struct bpf_btf_info info;
@@ -5873,6 +5890,9 @@ static int load_module_btfs(struct bpf_object *obj)
 	if (!kernel_supports(obj, FEAT_MODULE_BTF))
 		return 0;
 
+	if (obj->btf_module_allowlist_cnt == 0)
+		return 0;
+
 	while (true) {
 		err = bpf_btf_get_next_id(id, &id);
 		if (err && errno == ENOENT)
@@ -5915,6 +5935,11 @@ static int load_module_btfs(struct bpf_object *obj)
 			continue;
 		}
 
+		if (!is_btf_mod_allowed(obj, name)) {
+			close(fd);
+			continue;
+		}
+
 		btf = btf_get_from_fd(fd, obj->btf_vmlinux);
 		err = libbpf_get_error(btf);
 		if (err) {
@@ -5939,6 +5964,9 @@ static int load_module_btfs(struct bpf_object *obj)
 			break;
 		}
 		obj->btf_module_cnt++;
+
+		if (obj->btf_module_allowlist_cnt == obj->btf_module_cnt)
+			break;
 	}
 
 	if (err) {
@@ -8481,6 +8509,8 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 					  const struct bpf_object_open_opts *opts)
 {
 	const char *kconfig, *btf_tmp_path, *token_path;
+	size_t mod_allow_cnt, i, j;
+	const char **mod_allow;
 	struct bpf_object *obj;
 	int err;
 	char *log_buf;
@@ -8525,6 +8555,22 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 	if (token_path && strlen(token_path) >= PATH_MAX)
 		return ERR_PTR(-ENAMETOOLONG);
 
+	mod_allow = OPTS_GET(opts, btf_module_allowlist, NULL);
+	mod_allow_cnt = OPTS_GET(opts, btf_module_allowlist_cnt, 0);
+
+	if (mod_allow_cnt > SSIZE_MAX || (!mod_allow && mod_allow_cnt > 0))
+		return ERR_PTR(-EINVAL);
+
+	for (i = 0; i < mod_allow_cnt; i++) {
+		if (!mod_allow[i] || !mod_allow[i][0])
+			return ERR_PTR(-EINVAL);
+
+		for (j = 0; j < i; j++) {
+			if (strcmp(mod_allow[i], mod_allow[j]) == 0)
+				return ERR_PTR(-EINVAL);
+		}
+	}
+
 	obj = bpf_object__new(path, obj_buf, obj_buf_sz, obj_name);
 	if (IS_ERR(obj))
 		return obj;
@@ -8563,6 +8609,24 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 		}
 	}
 
+	obj->btf_module_allowlist_cnt = mod_allow ? mod_allow_cnt : -1;
+	if (mod_allow_cnt > 0) {
+		obj->btf_module_allowlist =
+			calloc(mod_allow_cnt, sizeof(*obj->btf_module_allowlist));
+		if (!obj->btf_module_allowlist) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		for (i = 0; i < mod_allow_cnt; i++) {
+			obj->btf_module_allowlist[i] = strdup(mod_allow[i]);
+			if (!obj->btf_module_allowlist[i]) {
+				err = -ENOMEM;
+				goto out;
+			}
+		}
+	}
+
 	err = bpf_object__elf_init(obj);
 	err = err ? : bpf_object__elf_collect(obj);
 	err = err ? : bpf_object__collect_externs(obj);
@@ -9684,6 +9748,12 @@ void bpf_object__close(struct bpf_object *obj)
 		close(obj->jumptable_maps[i].fd);
 	zfree(&obj->jumptable_maps);
 
+	if (obj->btf_module_allowlist) {
+		for (i = 0; i < obj->btf_module_allowlist_cnt; i++)
+			zfree(&obj->btf_module_allowlist[i]);
+		zfree(&obj->btf_module_allowlist);
+	}
+
 	free(obj);
 }
 
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index b965ad5715403..89768244d5d9e 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -224,10 +224,24 @@ struct bpf_object_open_opts {
 	 * point (/sys/fs/bpf), in case this default behavior is undesirable.
 	 */
 	const char *bpf_token_path;
+	/*
+	 * Optional allowlist of kernel module names whose BTFs libbpf is
+	 * allowed to load. The allowlist limits which kernel module BTFs libbpf
+	 * will consult wherever module BTF might be needed.
+	 *
+	 * When the option is not specified, the existing behavior remains
+	 * unchanged. An explicitly specified empty list prevents libbpf from
+	 * consulting any kernel module BTFs.
+	 *
+	 * The list must contain valid, non-empty module names and must not
+	 * contain duplicate entries; otherwise -EINVAL is returned.
+	 */
+	const char **btf_module_allowlist;
+	size_t btf_module_allowlist_cnt;
 
 	size_t :0;
 };
-#define bpf_object_open_opts__last_field bpf_token_path
+#define bpf_object_open_opts__last_field btf_module_allowlist_cnt
 
 /**
  * @brief **bpf_object__open()** creates a bpf_object by opening
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
new file mode 100644
index 0000000000000..3189133ee58b6
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include "btf_module_allowlist.skel.h"
+
+static void test_load_with_list(const char **list, size_t count,
+				int expected_ret)
+{
+	struct btf_module_allowlist *skel;
+	int ret;
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_allowlist = list,
+		.btf_module_allowlist_cnt = count,
+	);
+
+	skel = btf_module_allowlist__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_allowlist__open_opts"))
+		return;
+
+	ret = btf_module_allowlist__load(skel);
+	ASSERT_EQ(ret, expected_ret, "btf_module_allowlist__load");
+
+	btf_module_allowlist__destroy(skel);
+}
+
+static void btf_module_allowlist_default(void)
+{
+	struct btf_module_allowlist *skel;
+	int ret;
+
+	skel = btf_module_allowlist__open();
+	if (!ASSERT_OK_PTR(skel, "btf_module_allowlist__open"))
+		return;
+
+	ret = btf_module_allowlist__load(skel);
+	ASSERT_OK(ret, "btf_module_allowlist__load");
+
+	btf_module_allowlist__destroy(skel);
+}
+
+static void btf_module_allowlist_allow(void)
+{
+	const char *mod_list[] = { "bpf_testmod" };
+
+	test_load_with_list(mod_list, 1, 0);
+}
+
+/*
+ * A non-NULL allowlist with a count of 0 explicitly disables module BTFs.
+ * The pointer must be non-NULL to distinguish this from an unspecified
+ * allowlist.
+ */
+static void btf_module_allowlist_emptylist(void)
+{
+	const char *mod_list[] = { NULL };
+
+	test_load_with_list(mod_list, 0, -ESRCH);
+}
+
+/*
+ * bpf_testmod is not in the allowlist, so its BTF should not be loaded.
+ */
+static void btf_module_allowlist_skipunlisted(void)
+{
+	const char *mod_list[] = { "module_nonexist" };
+	struct btf_module_allowlist *skel;
+	int ret;
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_allowlist = mod_list,
+		.btf_module_allowlist_cnt = 1,
+	);
+
+	skel = btf_module_allowlist__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_allowlist__open_opts"))
+		return;
+
+	ret = bpf_program__set_attach_target(skel->progs.test_btf_module_allowlist, 0,
+					     "bpf_testmod:bpf_testmod_fentry_test1");
+	ASSERT_EQ(ret, -ESRCH, "bpf_program__set_attach_target");
+	btf_module_allowlist__destroy(skel);
+}
+
+static void test_invalid_input(const char **list, size_t count,
+			       const char *test_name)
+{
+	struct btf_module_allowlist *skel;
+	char assert_name[64];
+	int err;
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_allowlist = list,
+		.btf_module_allowlist_cnt = count,
+	);
+
+	snprintf(assert_name, sizeof(assert_name), "%s: open_opts", test_name);
+	skel = btf_module_allowlist__open_opts(&opts);
+	err = errno;
+	if (!ASSERT_NULL(skel, assert_name)) {
+		btf_module_allowlist__destroy(skel);
+		return;
+	}
+	snprintf(assert_name, sizeof(assert_name), "%s: open_opts err", test_name);
+	ASSERT_EQ(err, EINVAL, assert_name);
+}
+
+static void btf_module_allowlist_invalidinput(void)
+{
+	const char *names[] = { NULL };
+	const char *empty_names[] = { "" };
+	const char *duplicate_names[] = { "bpf_testmod", "bpf_testmod" };
+
+	test_invalid_input(NULL, 1, "null_list");
+	test_invalid_input(names, 1, "null_name");
+	test_invalid_input(empty_names, 1, "empty_name");
+	test_invalid_input(duplicate_names, 2, "duplicate_name");
+	test_invalid_input(names, (size_t)SSIZE_MAX + 1, "large_count");
+}
+
+void test_btf_module_allowlist(void)
+{
+	struct btf *vmlinux_btf = NULL;
+	struct btf *module_btf = NULL;
+
+	if (!env.has_testmod) {
+		printf("%s:SKIP: bpf_testmod is not available\n", __func__);
+		test__skip();
+		return;
+	}
+
+	vmlinux_btf = btf__load_vmlinux_btf();
+	if (!vmlinux_btf) {
+		printf("%s:SKIP: vmlinux_btf is not available\n", __func__);
+		test__skip();
+		return;
+	}
+
+	/* Ensure bpf_testmod BTF is available. */
+	module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
+	if (!module_btf) {
+		printf("%s:SKIP: bpf_testmod's BTF is not available\n", __func__);
+		btf__free(vmlinux_btf);
+		test__skip();
+		return;
+	}
+
+	btf__free(module_btf);
+	btf__free(vmlinux_btf);
+
+	if (test__start_subtest("btf_module_allowlist_default"))
+		btf_module_allowlist_default();
+
+	if (test__start_subtest("btf_module_allowlist_allow"))
+		btf_module_allowlist_allow();
+
+	if (test__start_subtest("btf_module_allowlist_skipunlisted"))
+		btf_module_allowlist_skipunlisted();
+
+	if (test__start_subtest("btf_module_allowlist_emptylist"))
+		btf_module_allowlist_emptylist();
+
+	if (test__start_subtest("btf_module_allowlist_invalidinput"))
+		btf_module_allowlist_invalidinput();
+}
diff --git a/tools/testing/selftests/bpf/progs/btf_module_allowlist.c b/tools/testing/selftests/bpf/progs/btf_module_allowlist.c
new file mode 100644
index 0000000000000..bb3c38bee77d6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/btf_module_allowlist.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+SEC("fentry/bpf_testmod_loop_test")
+int BPF_PROG(test_btf_module_allowlist)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";

3/2 2026/09/15 16:16 llm patch-evaluator
46m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 7147
							output: 94
							thoughts: 1171
						

Thoughts:
**Analysis of the Provided Patch Series**

Okay, so I'm presented with a patch series applied as a HEAD commit, and the goal is to determine if it's "WorthFuzzing." Let's break this down. First, I need to see what's actually changed.  The files modified are: `tools/lib/bpf/libbpf.c`, `tools/lib/bpf/libbpf.h`, `tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c`, and `tools/testing/selftests/bpf/progs/btf_module_allowlist.c`. All within the `tools/` directory.

Now, let me recall the crucial guidelines for determining "WorthFuzzing=false". According to section 2, I need to look for *negative criteria*: changes that *only* fall into specific categories. Namely, changes that are: non-kernel, non-executable, modifications to documentation, comments, spelling fixes, or user-space directories (self-tests, samples, scripts) that *don't* affect the compiled kernel image (`vmlinux`) or kernel modules.

A quick review of the diff confirms the modified files are all in either `tools/lib/bpf/` or `tools/testing/selftests/bpf/`. My initial thought process now is to verify whether any *other* files were touched in this commit. Thankfully, the full diff is provided, so I don't need to reach for `git status`, `git diff`, or a `grepper` call (which is really just a `git grep` anyways).  The provided diff confirms the list I started with is exhaustive. No other files are touched.

Alright. Considering the context, `tools/lib/bpf` contains the user-space libbpf library, and `tools/testing/selftests/bpf` are user-space self-tests.  My assessment is that none of these changes affect the core kernel or the kernel modules.

I go back to the guidelines once more, just to be absolutely sure. Yep, the changes strictly fall under the "user-space directories, self-tests... that do not affect the compiled kernel image (vmlinux) or kernel modules" category. Perfect. This is a clear case for `WorthFuzzing=false`. The changes are strictly confined to user-space tools and tests, and there is no interaction with kernel code.

Therefore, my conclusions are that `WorthFuzzing=false`, `FocusSymbols=[]`, and `EnableConfigs=[]`.  My reasoning is concisely explained: changes are isolated to user-space libbpf and self-tests located under the `tools/` directory, and the changes do not affect the kernel code. Time to call `set-results`.



4/2 2026/09/15 17:02 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch modifies only user-space code within tools/lib/bpf (libbpf library) and tools/testing/selftests/bpf (selftests). It does not alter the Linux kernel image or any kernel modules, so it is not applicable for syzkaller kernel fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies only user-space code within tools/lib/bpf (libbpf library) and tools/testing/selftests/bpf (selftests). It does not alter the Linux kernel image or any kernel modules, so it is not applicable for syzkaller kernel fuzzing.",
  "WorthFuzzing": false
}

5/1 2026/09/15 17:02 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)