AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/26 14:02 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch only modifies user-space tools (libbpf) and selftests. It does not make any functional changes to the kernel code itself.",
  "WorthFuzzing": false
}

1/1 2026/08/26 14:02 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit aea31ede05e44df0932202deddde0b5fe858fbec\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Aug 26 14:02:13 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 514e4e9daa825..1f061f4873b55 100644\n--- a/tools/lib/bpf/libbpf.c\n+++ b/tools/lib/bpf/libbpf.c\n@@ -779,6 +779,9 @@ struct bpf_object {\n \tchar *token_path;\n \tint token_fd;\n \n+\tchar **btf_module_names;\n+\tsize_t nr_btf_module_names;\n+\n \tchar path[];\n };\n \n@@ -5803,6 +5806,99 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,\n \treturn 0;\n }\n \n+static void bpf_object_free_btf_module_names(struct bpf_object *obj)\n+{\n+\tsize_t i;\n+\n+\tif (!obj-\u003ebtf_module_names)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c obj-\u003enr_btf_module_names; i++)\n+\t\tzfree(\u0026obj-\u003ebtf_module_names[i]);\n+\tzfree(\u0026obj-\u003ebtf_module_names);\n+\tobj-\u003enr_btf_module_names = 0;\n+}\n+\n+static int bpf_object_init_btf_module_names(struct bpf_object *obj,\n+\t\t\t\t\t    const struct bpf_object_open_opts *opts)\n+{\n+\tconst char **names;\n+\tsize_t i, j, cnt;\n+\tint err;\n+\n+\tnames = OPTS_GET(opts, btf_module_names, NULL);\n+\tif (!names)\n+\t\treturn 0;\n+\n+\tcnt = OPTS_GET(opts, nr_btf_module_names, 0);\n+\n+\t/*\n+\t * Allocate one entry for an empty list to distinguish it from the\n+\t * default behavior.\n+\t */\n+\tobj-\u003ebtf_module_names = calloc(cnt ?: 1,\n+\t\t\t\t       sizeof(*obj-\u003ebtf_module_names));\n+\tif (!obj-\u003ebtf_module_names)\n+\t\treturn -ENOMEM;\n+\n+\tfor (i = 0; i \u003c cnt; i++) {\n+\t\tif (!names[i] || !names[i][0]) {\n+\t\t\tpr_warn(\"invalid kernel module BTF name at index %zu\\n\", i);\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto err_out;\n+\t\t}\n+\n+\t\tfor (j = 0; j \u003c i; j++) {\n+\t\t\tif (strcmp(obj-\u003ebtf_module_names[j], names[i]) == 0) {\n+\t\t\t\tpr_warn(\"duplicate kernel module BTF name '%s'\\n\",\n+\t\t\t\t\tnames[i]);\n+\t\t\t\terr = -EINVAL;\n+\t\t\t\tgoto err_out;\n+\t\t\t}\n+\t\t}\n+\n+\t\tobj-\u003ebtf_module_names[i] = strdup(names[i]);\n+\t\tif (!obj-\u003ebtf_module_names[i]) {\n+\t\t\terr = -ENOMEM;\n+\t\t\tgoto err_out;\n+\t\t}\n+\n+\t\tobj-\u003enr_btf_module_names++;\n+\t}\n+\treturn 0;\n+\n+err_out:\n+\tbpf_object_free_btf_module_names(obj);\n+\treturn err;\n+}\n+\n+static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)\n+{\n+\tsize_t i;\n+\n+\tif (!obj-\u003ebtf_module_names)\n+\t\treturn true;\n+\n+\tfor (i = 0; i \u003c obj-\u003enr_btf_module_names; i++) {\n+\t\tif (strcmp(obj-\u003ebtf_module_names[i], name) == 0)\n+\t\t\treturn true;\n+\t}\n+\n+\tpr_debug(\"skipping module BTF '%s', not in btf_module_names\\n\", name);\n+\treturn false;\n+}\n+\n+static bool no_module_btfs_needed(const struct bpf_object *obj)\n+{\n+\treturn obj-\u003ebtf_module_names \u0026\u0026 !obj-\u003enr_btf_module_names;\n+}\n+\n+static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)\n+{\n+\treturn obj-\u003ebtf_module_names \u0026\u0026\n+\t       obj-\u003enr_btf_module_names == obj-\u003ebtf_module_cnt;\n+}\n+\n static int load_module_btfs(struct bpf_object *obj)\n {\n \tstruct bpf_btf_info info;\n@@ -5825,6 +5921,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 (no_module_btfs_needed(obj))\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@@ -5867,6 +5966,11 @@ static int load_module_btfs(struct bpf_object *obj)\n \t\t\tcontinue;\n \t\t}\n \n+\t\tif (!is_module_btf_needed(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@@ -5891,6 +5995,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 (all_needed_module_btfs_loaded(obj))\n+\t\t\tbreak;\n \t}\n \n \tif (err) {\n@@ -8508,6 +8615,10 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,\n \t\t}\n \t}\n \n+\terr = bpf_object_init_btf_module_names(obj, opts);\n+\tif (err)\n+\t\tgoto out;\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@@ -9629,6 +9740,8 @@ void bpf_object__close(struct bpf_object *obj)\n \t\tclose(obj-\u003ejumptable_maps[i].fd);\n \tzfree(\u0026obj-\u003ejumptable_maps);\n \n+\tbpf_object_free_btf_module_names(obj);\n+\n \tfree(obj);\n }\n \ndiff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h\nindex b965ad5715403..b8c1d98d69d85 100644\n--- a/tools/lib/bpf/libbpf.h\n+++ b/tools/lib/bpf/libbpf.h\n@@ -224,10 +224,32 @@ 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 list of kernel module names whose BTFs should be loaded.\n+\t * nr_btf_module_names specifies the number of entries in\n+\t * btf_module_names.\n+\t *\n+\t * With btf_module_names:\n+\t * - when provided, only the BTFs of the specified modules are loaded;\n+\t * - when an empty list is provided, no module BTFs are loaded;\n+\t * - when NULL, all module BTFs are loaded as before.\n+\t *\n+\t * The list must not contain duplicate entries; otherwise -EINVAL is\n+\t * returned.\n+\t *\n+\t * This affects:\n+\t * - BPF CO-RE relocations against types defined in modules;\n+\t * - BTF-based resolution of function attach targets for\n+\t *   fentry/fexit/fmod_ret/freplace/LSM programs;\n+\t * - struct_ops kernel type resolution;\n+\t * - extern (ksym) resolution for kernel symbols defined in modules.\n+\t */\n+\tconst char **btf_module_names;\n+\tsize_t nr_btf_module_names;\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 nr_btf_module_names\n \n /**\n  * @brief **bpf_object__open()** creates a bpf_object by opening\ndiff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c\nnew file mode 100644\nindex 0000000000000..3dc72bef014c8\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c\n@@ -0,0 +1,139 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003ctest_progs.h\u003e\n+#include \"btf_module_names.skel.h\"\n+\n+static void btf_module_names_load(void)\n+{\n+\tstruct btf_module_names *skel = NULL;\n+\tint ret;\n+\tstatic const char *mod_names[] = { \"bpf_testmod\" };\n+\n+\tLIBBPF_OPTS(bpf_object_open_opts, opts,\n+\t\t.btf_module_names = mod_names,\n+\t\t.nr_btf_module_names = 1,\n+\t);\n+\n+\t/* Verify backward compatibility. */\n+\tskel = btf_module_names__open_opts(NULL);\n+\tif (!ASSERT_OK_PTR(skel, \"btf_module_names__open_opts default\"))\n+\t\tgoto out;\n+\n+\tret = btf_module_names__load(skel);\n+\tASSERT_OK(ret, \"btf_module_names__load default\");\n+\n+\tbtf_module_names__destroy(skel);\n+\tskel = NULL;\n+\n+\tskel = btf_module_names__open_opts(\u0026opts);\n+\tif (!ASSERT_OK_PTR(skel, \"btf_module_names__open_opts\"))\n+\t\tgoto out;\n+\n+\tret = btf_module_names__load(skel);\n+\tASSERT_OK(ret, \"btf_module_names__load\");\n+out:\n+\tbtf_module_names__destroy(skel);\n+}\n+\n+/*\n+ * Verify that an unrequested module BTF is skipped. The BPF program\n+ * requires the BTF of bpf_testmod, but bpf_testmod is not specified in\n+ * btf_module_names, so its BTF is skipped and the BPF program fails to load.\n+ */\n+static void btf_module_names_skip(void)\n+{\n+\tstruct btf_module_names *skel = NULL;\n+\tint ret;\n+\tstatic const char *mod_names[] = { \"module_nonexist\" };\n+\n+\tLIBBPF_OPTS(bpf_object_open_opts, opts,\n+\t\t.btf_module_names = mod_names,\n+\t\t.nr_btf_module_names = 1,\n+\t);\n+\n+\tskel = btf_module_names__open_opts(\u0026opts);\n+\tif (!ASSERT_OK_PTR(skel, \"btf_module_names__open_opts\"))\n+\t\tgoto out;\n+\n+\tret = btf_module_names__load(skel);\n+\tASSERT_EQ(ret, -ESRCH, \"btf_module_names__load\");\n+\n+out:\n+\tbtf_module_names__destroy(skel);\n+}\n+\n+/*\n+ * Verify that an empty filter skips loading all module BTFs. The BPF\n+ * program requires bpf_testmod BTF, so it fails to load.\n+ */\n+static void btf_module_names_empty(void)\n+{\n+\tstruct btf_module_names *skel = NULL;\n+\tint ret;\n+\tstatic const char *mod_names[] = { \"\" };\n+\n+\tLIBBPF_OPTS(bpf_object_open_opts, opts,\n+\t\t.btf_module_names = mod_names,\n+\t);\n+\n+\tskel = btf_module_names__open_opts(\u0026opts);\n+\tif (!ASSERT_OK_PTR(skel, \"btf_module_names__open_opts empty\"))\n+\t\tgoto out;\n+\n+\tret = btf_module_names__load(skel);\n+\tASSERT_EQ(ret, -ESRCH, \"btf_module_names__load empty\");\n+\n+out:\n+\tbtf_module_names__destroy(skel);\n+}\n+\n+static void btf_module_names_invalid(void)\n+{\n+\tstruct btf_module_names *skel = NULL;\n+\tconst char *names[] = { NULL };\n+\tconst char *empty_names[] = { \"\" };\n+\tconst char *duplicate_names[] = {\n+\t\t\"bpf_testmod\", \"bpf_testmod\",\n+\t};\n+\tLIBBPF_OPTS(bpf_object_open_opts, opts,\n+\t\t.btf_module_names = names,\n+\t\t.nr_btf_module_names = 1,\n+\t);\n+\n+\tskel = btf_module_names__open_opts(\u0026opts);\n+\tASSERT_EQ(libbpf_get_error(skel), -EINVAL,\n+\t\t  \"btf_module_names__open_opts null\");\n+\tbtf_module_names__destroy(skel);\n+\n+\topts.btf_module_names = empty_names;\n+\tskel = btf_module_names__open_opts(\u0026opts);\n+\tASSERT_EQ(libbpf_get_error(skel), -EINVAL,\n+\t\t  \"btf_module_names__open_opts empty\");\n+\tbtf_module_names__destroy(skel);\n+\n+\topts.btf_module_names = duplicate_names;\n+\topts.nr_btf_module_names = 2;\n+\tskel = btf_module_names__open_opts(\u0026opts);\n+\tASSERT_EQ(libbpf_get_error(skel), -EINVAL,\n+\t\t  \"btf_module_names__open_opts duplicate name\");\n+\tbtf_module_names__destroy(skel);\n+}\n+\n+void test_btf_module_names(void)\n+{\n+\tif (!env.has_testmod) {\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tif (test__start_subtest(\"btf_module_names_load\"))\n+\t\tbtf_module_names_load();\n+\n+\tif (test__start_subtest(\"btf_module_names_skip\"))\n+\t\tbtf_module_names_skip();\n+\n+\tif (test__start_subtest(\"btf_module_names_empty\"))\n+\t\tbtf_module_names_empty();\n+\n+\tif (test__start_subtest(\"btf_module_names_invalid\"))\n+\t\tbtf_module_names_invalid();\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/btf_module_names.c b/tools/testing/selftests/bpf/progs/btf_module_names.c\nnew file mode 100644\nindex 0000000000000..232adc0f33f6e\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/btf_module_names.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_names)\n+{\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n"
}

2/1 2026/08/26 14:02 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space tools (libbpf) and selftests. It does not make any functional changes to the kernel code itself.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a '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:
For your convenience, here is the diff of the changes:
commit aea31ede05e44df0932202deddde0b5fe858fbec
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Aug 26 14:02:13 2026 +0000

    syz-cluster: applied patch under review

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 514e4e9daa825..1f061f4873b55 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -779,6 +779,9 @@ struct bpf_object {
 	char *token_path;
 	int token_fd;
 
+	char **btf_module_names;
+	size_t nr_btf_module_names;
+
 	char path[];
 };
 
@@ -5803,6 +5806,99 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
 	return 0;
 }
 
+static void bpf_object_free_btf_module_names(struct bpf_object *obj)
+{
+	size_t i;
+
+	if (!obj->btf_module_names)
+		return;
+
+	for (i = 0; i < obj->nr_btf_module_names; i++)
+		zfree(&obj->btf_module_names[i]);
+	zfree(&obj->btf_module_names);
+	obj->nr_btf_module_names = 0;
+}
+
+static int bpf_object_init_btf_module_names(struct bpf_object *obj,
+					    const struct bpf_object_open_opts *opts)
+{
+	const char **names;
+	size_t i, j, cnt;
+	int err;
+
+	names = OPTS_GET(opts, btf_module_names, NULL);
+	if (!names)
+		return 0;
+
+	cnt = OPTS_GET(opts, nr_btf_module_names, 0);
+
+	/*
+	 * Allocate one entry for an empty list to distinguish it from the
+	 * default behavior.
+	 */
+	obj->btf_module_names = calloc(cnt ?: 1,
+				       sizeof(*obj->btf_module_names));
+	if (!obj->btf_module_names)
+		return -ENOMEM;
+
+	for (i = 0; i < cnt; i++) {
+		if (!names[i] || !names[i][0]) {
+			pr_warn("invalid kernel module BTF name at index %zu\n", i);
+			err = -EINVAL;
+			goto err_out;
+		}
+
+		for (j = 0; j < i; j++) {
+			if (strcmp(obj->btf_module_names[j], names[i]) == 0) {
+				pr_warn("duplicate kernel module BTF name '%s'\n",
+					names[i]);
+				err = -EINVAL;
+				goto err_out;
+			}
+		}
+
+		obj->btf_module_names[i] = strdup(names[i]);
+		if (!obj->btf_module_names[i]) {
+			err = -ENOMEM;
+			goto err_out;
+		}
+
+		obj->nr_btf_module_names++;
+	}
+	return 0;
+
+err_out:
+	bpf_object_free_btf_module_names(obj);
+	return err;
+}
+
+static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)
+{
+	size_t i;
+
+	if (!obj->btf_module_names)
+		return true;
+
+	for (i = 0; i < obj->nr_btf_module_names; i++) {
+		if (strcmp(obj->btf_module_names[i], name) == 0)
+			return true;
+	}
+
+	pr_debug("skipping module BTF '%s', not in btf_module_names\n", name);
+	return false;
+}
+
+static bool no_module_btfs_needed(const struct bpf_object *obj)
+{
+	return obj->btf_module_names && !obj->nr_btf_module_names;
+}
+
+static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)
+{
+	return obj->btf_module_names &&
+	       obj->nr_btf_module_names == obj->btf_module_cnt;
+}
+
 static int load_module_btfs(struct bpf_object *obj)
 {
 	struct bpf_btf_info info;
@@ -5825,6 +5921,9 @@ static int load_module_btfs(struct bpf_object *obj)
 	if (!kernel_supports(obj, FEAT_MODULE_BTF))
 		return 0;
 
+	if (no_module_btfs_needed(obj))
+		return 0;
+
 	while (true) {
 		err = bpf_btf_get_next_id(id, &id);
 		if (err && errno == ENOENT)
@@ -5867,6 +5966,11 @@ static int load_module_btfs(struct bpf_object *obj)
 			continue;
 		}
 
+		if (!is_module_btf_needed(obj, name)) {
+			close(fd);
+			continue;
+		}
+
 		btf = btf_get_from_fd(fd, obj->btf_vmlinux);
 		err = libbpf_get_error(btf);
 		if (err) {
@@ -5891,6 +5995,9 @@ static int load_module_btfs(struct bpf_object *obj)
 			break;
 		}
 		obj->btf_module_cnt++;
+
+		if (all_needed_module_btfs_loaded(obj))
+			break;
 	}
 
 	if (err) {
@@ -8508,6 +8615,10 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 		}
 	}
 
+	err = bpf_object_init_btf_module_names(obj, opts);
+	if (err)
+		goto out;
+
 	err = bpf_object__elf_init(obj);
 	err = err ? : bpf_object__elf_collect(obj);
 	err = err ? : bpf_object__collect_externs(obj);
@@ -9629,6 +9740,8 @@ void bpf_object__close(struct bpf_object *obj)
 		close(obj->jumptable_maps[i].fd);
 	zfree(&obj->jumptable_maps);
 
+	bpf_object_free_btf_module_names(obj);
+
 	free(obj);
 }
 
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index b965ad5715403..b8c1d98d69d85 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -224,10 +224,32 @@ struct bpf_object_open_opts {
 	 * point (/sys/fs/bpf), in case this default behavior is undesirable.
 	 */
 	const char *bpf_token_path;
+	/*
+	 * Optional list of kernel module names whose BTFs should be loaded.
+	 * nr_btf_module_names specifies the number of entries in
+	 * btf_module_names.
+	 *
+	 * With btf_module_names:
+	 * - when provided, only the BTFs of the specified modules are loaded;
+	 * - when an empty list is provided, no module BTFs are loaded;
+	 * - when NULL, all module BTFs are loaded as before.
+	 *
+	 * The list must not contain duplicate entries; otherwise -EINVAL is
+	 * returned.
+	 *
+	 * This affects:
+	 * - BPF CO-RE relocations against types defined in modules;
+	 * - BTF-based resolution of function attach targets for
+	 *   fentry/fexit/fmod_ret/freplace/LSM programs;
+	 * - struct_ops kernel type resolution;
+	 * - extern (ksym) resolution for kernel symbols defined in modules.
+	 */
+	const char **btf_module_names;
+	size_t nr_btf_module_names;
 
 	size_t :0;
 };
-#define bpf_object_open_opts__last_field bpf_token_path
+#define bpf_object_open_opts__last_field nr_btf_module_names
 
 /**
  * @brief **bpf_object__open()** creates a bpf_object by opening
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
new file mode 100644
index 0000000000000..3dc72bef014c8
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "btf_module_names.skel.h"
+
+static void btf_module_names_load(void)
+{
+	struct btf_module_names *skel = NULL;
+	int ret;
+	static const char *mod_names[] = { "bpf_testmod" };
+
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_names = mod_names,
+		.nr_btf_module_names = 1,
+	);
+
+	/* Verify backward compatibility. */
+	skel = btf_module_names__open_opts(NULL);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts default"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_OK(ret, "btf_module_names__load default");
+
+	btf_module_names__destroy(skel);
+	skel = NULL;
+
+	skel = btf_module_names__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_OK(ret, "btf_module_names__load");
+out:
+	btf_module_names__destroy(skel);
+}
+
+/*
+ * Verify that an unrequested module BTF is skipped. The BPF program
+ * requires the BTF of bpf_testmod, but bpf_testmod is not specified in
+ * btf_module_names, so its BTF is skipped and the BPF program fails to load.
+ */
+static void btf_module_names_skip(void)
+{
+	struct btf_module_names *skel = NULL;
+	int ret;
+	static const char *mod_names[] = { "module_nonexist" };
+
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_names = mod_names,
+		.nr_btf_module_names = 1,
+	);
+
+	skel = btf_module_names__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load");
+
+out:
+	btf_module_names__destroy(skel);
+}
+
+/*
+ * Verify that an empty filter skips loading all module BTFs. The BPF
+ * program requires bpf_testmod BTF, so it fails to load.
+ */
+static void btf_module_names_empty(void)
+{
+	struct btf_module_names *skel = NULL;
+	int ret;
+	static const char *mod_names[] = { "" };
+
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_names = mod_names,
+	);
+
+	skel = btf_module_names__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts empty"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load empty");
+
+out:
+	btf_module_names__destroy(skel);
+}
+
+static void btf_module_names_invalid(void)
+{
+	struct btf_module_names *skel = NULL;
+	const char *names[] = { NULL };
+	const char *empty_names[] = { "" };
+	const char *duplicate_names[] = {
+		"bpf_testmod", "bpf_testmod",
+	};
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_names = names,
+		.nr_btf_module_names = 1,
+	);
+
+	skel = btf_module_names__open_opts(&opts);
+	ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+		  "btf_module_names__open_opts null");
+	btf_module_names__destroy(skel);
+
+	opts.btf_module_names = empty_names;
+	skel = btf_module_names__open_opts(&opts);
+	ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+		  "btf_module_names__open_opts empty");
+	btf_module_names__destroy(skel);
+
+	opts.btf_module_names = duplicate_names;
+	opts.nr_btf_module_names = 2;
+	skel = btf_module_names__open_opts(&opts);
+	ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+		  "btf_module_names__open_opts duplicate name");
+	btf_module_names__destroy(skel);
+}
+
+void test_btf_module_names(void)
+{
+	if (!env.has_testmod) {
+		test__skip();
+		return;
+	}
+
+	if (test__start_subtest("btf_module_names_load"))
+		btf_module_names_load();
+
+	if (test__start_subtest("btf_module_names_skip"))
+		btf_module_names_skip();
+
+	if (test__start_subtest("btf_module_names_empty"))
+		btf_module_names_empty();
+
+	if (test__start_subtest("btf_module_names_invalid"))
+		btf_module_names_invalid();
+}
diff --git a/tools/testing/selftests/bpf/progs/btf_module_names.c b/tools/testing/selftests/bpf/progs/btf_module_names.c
new file mode 100644
index 0000000000000..232adc0f33f6e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/btf_module_names.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_names)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";

3/2 2026/08/26 14:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 6083
							output: 62
							thoughts: 330
						

Thoughts:
**My Assessment: No Kernel Fuzzing Needed**

Okay, so this patch is touching `tools/lib/bpf/libbpf.c`, `tools/lib/bpf/libbpf.h`, and some selftests. No kernel code is being touched. Excellent, I like seeing that distinction. No modifications under `kernel/`, `net/`, `drivers/`, or any of those core kernel directories. This is purely user-space stuff: libbpf, and tests for that lib.

Now, the crucial question: does this affect the kernel's fuzzing strategy? Syzkaller, for instance, fuzzes the kernel *through* syscalls, right? It doesn't directly target libbpf. It might *use* libbpf as part of its setup, but the patch itself is confined to user-space code. Since the changes are contained within the user-space tooling, specifically in the libbpf and its tests, this isn't going to introduce any new attack surfaces for a kernel fuzzer like Syzkaller.

Let me just double-check one more time... okay, confirming the list of files modified: `tools/lib/bpf/libbpf.c`, `tools/lib/bpf/libbpf.h`, `tools/testing/selftests/bpf/prog_tests/btf_module_names.c`, and `tools/testing/selftests/bpf/progs/btf_module_names.c`. Yep, all under `tools/`. No kernel modifications detected.

Therefore, I'm confident in setting `WorthFuzzing=false`. This patch doesn't warrant kernel-level fuzzing.



4/2 2026/08/26 14:02 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space tools (libbpf) and selftests. It does not make any functional changes to the kernel code itself.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space tools (libbpf) and selftests. It does not make any functional changes to the kernel code itself.",
  "WorthFuzzing": false
}

5/1 2026/08/26 14: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)