* [PATCH bpf-next v7 0/2] libbpf: improve BPF load performance by selectively loading module BTFs
@ 2026-09-07 5:38 Fuyu Zhao
2026-09-07 5:38 ` [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
2026-09-07 5:38 ` [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
0 siblings, 2 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-07 5:38 UTC (permalink / raw)
To: bpf; +Cc: eddyz87, andrii.nakryiko, alan.maguire, Fuyu Zhao
Currently, load_module_btfs() unconditionally iterates over all kernel
module BTFs and loads each one. This introduces unnecessary overhead
when a BPF program only needs BTFs from a small subset of modules.
In our Android testing, BPF programs are loaded on demand rather than
preloaded to avoid unnecessary memory usage from unused programs. With
93 module BTFs present, the total BPF loading time exceeds 300 ms, with
module BTF loading accounting for around 69% of the total loading time.
The existing module qualification in SEC(), such as
SEC("fentry/mymod:foo"), does not reduce this overhead, as module BTFs
are loaded before the module-qualified target is resolved.
Use btf_module_allowlist and btf_module_allowlist_cnt to skip unrelated
module BTFs during iteration and stop once all allowed module BTFs have
been loaded. When the allowlist is not specified, the existing behavior
remains unchanged.
Performance impact (BPF skeleton open and load time):
Module BTFs | Default | Selective loading | Time reduction
------------|---------|-------------------|---------------
1 | 35.6 ms | 35.6 ms | Baseline
10 | 37.2 ms | 35.7 ms | 4.0%
100 | 46.7 ms | 36.5 ms | 21.8%
300 | 65.2 ms | 38.5 ms | 40.9%
Changelog:
v7:
- Rename btf_module_names to btf_module_allowlist and use _cnt naming.
(Andrii)
- Simplify allowlist handling and documentation, and drop unnecessary
logging, cleanup, and helper logic. (Andrii)
- Simplify selftests, add invalid allowlist/count coverage, and add skip
reasons for missing BTF prerequisites. (Andrii, bot+bpf-ci)
v6:
- Link: https://lore.kernel.org/bpf/20260831133809.1161-1-zhaofuyu@vivo.com/
- Document module-qualified tracing multi-attach targets and invalid
module name handling. (bot+bpf-ci)
- Remove unnecessary `static` qualifiers from test module name arrays.
(bot+bpf-ci)
- Check that bpf_testmod BTF is available before running the tests.
(bot+bpf-ci)
v5:
- Link: https://lore.kernel.org/bpf/20260826131428.302-1-zhaofuyu@vivo.com/
- Add no_module_btfs_needed() to skip loading module BTFs early when the
module BTF name list is empty. (sashiko-bot)
- Document struct_ops as an affected use case for btf_module_names.
(bot+bpf-ci)
- Add invalid input tests and assert expected error codes for negative
cases. (bot+bpf-ci)
v4:
- Link: https://lore.kernel.org/bpf/20260824125310.1384-1-zhaofuyu@vivo.com/
- Simplify and rename newly added struct members and their documentation.
(Eduard)
- Use array search instead of a hashmap for module name matching to
simplify the code. (Eduard)
- Allow an empty module name list to skip loading all module BTFs.
(Eduard, sashiko-bot)
- Move btf_module_names option handling before bpf_object__elf_init().
(Eduard)
- Rename internal helpers to follow libbpf naming conventions. (Andrii)
- Reject duplicate module names and document the rejection rule. (Andrii)
- Replace log interception with functional tests and simplify selftests.
(Andrii)
v3:
- Link: https://lore.kernel.org/bpf/20260819090426.267-1-zhaofuyu@vivo.com/
- Use bpf_object_open_opts to specify kernel module BTFs to load rather
than introducing a new .kmod_btfs ELF section, as suggested by Andrii.
v2:
- Link: https://lore.kernel.org/bpf/20260813032613.2755-1-zhaofuyu@vivo.com/
- Addressed issues with allocation error handling, multiple .kmod_btfs
sections, the transient stack pointer in is_kmod_btf_needed(), and
selftest dependency and comment formatting. (sashiko-bot)
- Removed KMODS_BTF_LOADED and KMODS_BTF_UNLOADED and simplified the
related logic.
v1:
- Link: https://lore.kernel.org/bpf/20260806042042.3239428-1-zhaofuyu@vivo.com/
Fuyu Zhao (2):
libbpf: support selective kernel module BTF loading via
bpf_object_open_opts
selftests/bpf: add tests for selective module BTF loading
tools/lib/bpf/libbpf.c | 72 +++++++-
tools/lib/bpf/libbpf.h | 16 +-
.../bpf/prog_tests/btf_module_allowlist.c | 155 ++++++++++++++++++
.../bpf/progs/btf_module_allowlist.c | 13 ++
4 files changed, 254 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
create mode 100644 tools/testing/selftests/bpf/progs/btf_module_allowlist.c
--
2.34.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
2026-09-07 5:38 [PATCH bpf-next v7 0/2] libbpf: improve BPF load performance by selectively loading module BTFs Fuyu Zhao
@ 2026-09-07 5:38 ` Fuyu Zhao
2026-09-07 5:49 ` sashiko-bot
2026-09-07 8:33 ` Jiri Olsa
2026-09-07 5:38 ` [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
1 sibling, 2 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-07 5:38 UTC (permalink / raw)
To: bpf; +Cc: eddyz87, andrii.nakryiko, alan.maguire, Fuyu Zhao,
Andrii Nakryiko
Add btf_module_allowlist and btf_module_allowlist_cnt fields to
bpf_object_open_opts to limit which kernel module BTFs libbpf is
allowed to load.
When the option is not specified, the existing behavior remains
unchanged. An explicitly specified empty allowlist prevents libbpf from
consulting any kernel module BTFs.
The allowlist limits which kernel module BTFs libbpf will consult
wherever module BTF might be needed.
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
---
tools/lib/bpf/libbpf.c | 72 +++++++++++++++++++++++++++++++++++++++++-
tools/lib/bpf/libbpf.h | 16 +++++++++-
2 files changed, 86 insertions(+), 2 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 514e4e9daa82..581a4d8ee124 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -748,6 +748,8 @@ struct bpf_object {
bool btf_modules_loaded;
size_t btf_module_cnt;
size_t btf_module_cap;
+ char **btf_module_allowlist;
+ int btf_module_allowlist_cnt;
/* optional log settings passed to BPF_BTF_LOAD and BPF_PROG_LOAD commands */
char *log_buf;
@@ -5803,6 +5805,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)
+{
+ int 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;
@@ -5825,6 +5842,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)
@@ -5867,6 +5887,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) {
@@ -5891,6 +5916,10 @@ static int load_module_btfs(struct bpf_object *obj)
break;
}
obj->btf_module_cnt++;
+
+ if (obj->btf_module_allowlist &&
+ obj->btf_module_allowlist_cnt == obj->btf_module_cnt)
+ break;
}
if (err) {
@@ -8426,8 +8455,10 @@ 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;
+ const char **mod_allow;
+ int mod_allow_cnt;
struct bpf_object *obj;
- int err;
+ int err, i, j;
char *log_buf;
size_t log_size;
__u32 log_level;
@@ -8470,6 +8501,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 && mod_allow_cnt > 0) || 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;
@@ -8508,6 +8555,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 (obj->btf_module_allowlist_cnt > 0) {
+ obj->btf_module_allowlist = calloc(obj->btf_module_allowlist_cnt,
+ sizeof(*obj->btf_module_allowlist));
+ if (!obj->btf_module_allowlist) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < obj->btf_module_allowlist_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);
@@ -9571,6 +9636,7 @@ static void bpf_map__destroy(struct bpf_map *map)
void bpf_object__close(struct bpf_object *obj)
{
size_t i;
+ int j;
if (IS_ERR_OR_NULL(obj))
return;
@@ -9629,6 +9695,10 @@ void bpf_object__close(struct bpf_object *obj)
close(obj->jumptable_maps[i].fd);
zfree(&obj->jumptable_maps);
+ for (j = 0; j < obj->btf_module_allowlist_cnt; j++)
+ zfree(&obj->btf_module_allowlist[j]);
+ zfree(&obj->btf_module_allowlist);
+
free(obj);
}
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index b965ad571540..b1602c769f3d 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;
+ int 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
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading
2026-09-07 5:38 [PATCH bpf-next v7 0/2] libbpf: improve BPF load performance by selectively loading module BTFs Fuyu Zhao
2026-09-07 5:38 ` [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
@ 2026-09-07 5:38 ` Fuyu Zhao
2026-09-07 5:51 ` sashiko-bot
` (2 more replies)
1 sibling, 3 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-07 5:38 UTC (permalink / raw)
To: bpf; +Cc: eddyz87, andrii.nakryiko, alan.maguire, Fuyu Zhao
Add selftests covering selective kernel module BTF loading through
bpf_object_open_opts.
The tests verify that:
- the existing behavior is preserved when the allowlist is not
specified;
- loading succeeds when the required module BTF is specified in the
allowlist;
- module BTFs not in the allowlist are skipped;
- an empty allowlist skips loading all module BTFs;
- invalid allowlist and module name inputs are rejected.
Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
---
.../bpf/prog_tests/btf_module_allowlist.c | 155 ++++++++++++++++++
.../bpf/progs/btf_module_allowlist.c | 13 ++
2 files changed, 168 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
create mode 100644 tools/testing/selftests/bpf/progs/btf_module_allowlist.c
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 000000000000..fb1b9ffb6c7a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
@@ -0,0 +1,155 @@
+// 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, int 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;
+ LIBBPF_OPTS(bpf_object_open_opts, opts);
+
+ 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);
+ skel = NULL;
+
+ 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_OK(ret, "btf_module_allowlist__load opts");
+
+ 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);
+}
+
+/*
+ * bpf_testmod is not in the allowlist, so loading should fail.
+ */
+static void btf_module_allowlist_skip(void)
+{
+ const char *mod_list[] = { "module_nonexist" };
+
+ test_load_with_list(mod_list, 1, -ESRCH);
+}
+
+/*
+ * An empty allowlist skips all module BTFs, so loading should fail.
+ */
+static void btf_module_allowlist_empty(void)
+{
+ const char *mod_list[] = { NULL };
+
+ test_load_with_list(mod_list, 0, -ESRCH);
+}
+
+static void test_invalid_input(const char **list, int count,
+ const char *test_name)
+{
+ struct btf_module_allowlist *skel;
+ char assert_name[64];
+ 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);
+ 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(errno, EINVAL, assert_name);
+}
+
+static void btf_module_allowlist_invalid(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(NULL, -1, "negative_count");
+ test_invalid_input(names, 1, "null_name");
+ test_invalid_input(empty_names, 1, "empty_name");
+ test_invalid_input(duplicate_names, 2, "duplicate_name");
+}
+
+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 (libbpf_get_error(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 (libbpf_get_error(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("default"))
+ btf_module_allowlist_default();
+
+ if (test__start_subtest("allowlist"))
+ btf_module_allowlist_allow();
+
+ if (test__start_subtest("skip_unlisted"))
+ btf_module_allowlist_skip();
+
+ if (test__start_subtest("emptylist"))
+ btf_module_allowlist_empty();
+
+ if (test__start_subtest("invalid_input"))
+ btf_module_allowlist_invalid();
+}
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 000000000000..bb3c38bee77d
--- /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";
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
2026-09-07 5:38 ` [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
@ 2026-09-07 5:49 ` sashiko-bot
2026-09-09 14:08 ` Fuyu Zhao
2026-09-07 8:33 ` Jiri Olsa
1 sibling, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-09-07 5:49 UTC (permalink / raw)
To: Fuyu Zhao; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] If `calloc` fails when allocating `btf_module_allowlist`, a NULL pointer dereference occurs during cleanup in `bpf_object__close()`.
--
commit 2653ece25005f3595512126454652c2d89f17f3d
Author: Fuyu Zhao <zhaofuyu@vivo.com>
libbpf: support selective kernel module BTF loading via bpf_object_open_opts
This commit adds a btf_module_allowlist field to bpf_object_open_opts to
limit which kernel module BTFs libbpf is allowed to load. When the option is
unspecified, behavior remains unchanged, while an explicitly empty list
prevents consulting any module BTFs.
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index c036e8a91ed88..d49e48c5c1417 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -8563,6 +8610,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 (obj->btf_module_allowlist_cnt > 0) {
> + obj->btf_module_allowlist = calloc(obj->btf_module_allowlist_cnt,
> + sizeof(*obj->btf_module_allowlist));
> + if (!obj->btf_module_allowlist) {
> + err = -ENOMEM;
> + goto out;
[Severity: High]
If this calloc fails, obj->btf_module_allowlist_cnt is left > 0 while
obj->btf_module_allowlist remains NULL. The goto out path will then call
bpf_object__close() to clean up the object.
> + }
> +
> + for (i = 0; i < obj->btf_module_allowlist_cnt; i++) {
> + obj->btf_module_allowlist[i] = strdup(mod_allow[i]);
> + if (!obj->btf_module_allowlist[i]) {
> + err = -ENOMEM;
> + goto out;
> + }
> + }
> + }
[ ... ]
> @@ -9684,6 +9750,10 @@ void bpf_object__close(struct bpf_object *obj)
> close(obj->jumptable_maps[i].fd);
> zfree(&obj->jumptable_maps);
>
> + for (j = 0; j < obj->btf_module_allowlist_cnt; j++)
> + zfree(&obj->btf_module_allowlist[j]);
[Severity: High]
Will this crash if the initial calloc failed in bpf_object_open()?
Because obj->btf_module_allowlist_cnt is > 0 but obj->btf_module_allowlist
is NULL, the loop will attempt to access &obj->btf_module_allowlist[j],
which evaluates to an offset from NULL. When zfree() tries to dereference
this pointer to free it, it will result in a NULL pointer dereference.
Should obj->btf_module_allowlist_cnt be cleared when calloc fails, or
should this loop check if obj->btf_module_allowlist is valid before looping?
> + zfree(&obj->btf_module_allowlist);
> +
> free(obj);
> }
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260907053847.1115-1-zhaofuyu@vivo.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading
2026-09-07 5:38 ` [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
@ 2026-09-07 5:51 ` sashiko-bot
2026-09-09 14:08 ` Fuyu Zhao
2026-09-07 8:34 ` Jiri Olsa
2026-09-08 16:03 ` bot+bpf-ci
2 siblings, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-09-07 5:51 UTC (permalink / raw)
To: Fuyu Zhao; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] Flaky Test / errno Clobbering
Pre-existing issues:
- [High] NULL Pointer Dereference in bpf_object__close() on allocation failure
--
commit b3d2f975120fb1ef7382fad8983c653ebd2ae39c
Author: Fuyu Zhao <zhaofuyu@vivo.com>
selftests/bpf: add tests for selective module BTF loading
This commit adds selftests for selective kernel module BTF loading using
bpf_object_open_opts. It verifies that valid allowlists succeed, module BTFs
not listed are skipped, and invalid inputs are rejected.
> 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..fb1b9ffb6c7aa
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
[ ... ]
> +static void test_invalid_input(const char **list, int count,
> + const char *test_name)
> +{
> + struct btf_module_allowlist *skel;
> + char assert_name[64];
> + 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);
[Severity: High]
This isn't a bug introduced by this patch, but does passing a negative count
or encountering an allocation failure here lead to a crash inside libbpf?
When btf_module_allowlist__open_opts() calls into bpf_object_open(), if the
count is huge (e.g., from passing -1 in the negative_count subtest) or calloc()
fails with ENOMEM, the array remains NULL:
tools/lib/bpf/libbpf.c:bpf_object_open() {
...
obj->btf_module_allowlist = calloc(obj->btf_module_allowlist_cnt,
sizeof(*obj->btf_module_allowlist));
if (!obj->btf_module_allowlist) {
err = -ENOMEM;
goto out; /* out calls bpf_object__close(obj) */
}
...
}
Then bpf_object__close() iterates over the unallocated array:
tools/lib/bpf/libbpf.c:bpf_object__close() {
...
for (j = 0; j < obj->btf_module_allowlist_cnt; j++)
zfree(&obj->btf_module_allowlist[j]);
...
}
Since obj->btf_module_allowlist is NULL but obj->btf_module_allowlist_cnt is
greater than 0 (or -1 cast to a large unsigned type), won't this blindly access
memory and crash?
> + 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(errno, EINVAL, assert_name);
[Severity: Medium]
Can this cause test flakiness?
According to POSIX, standard library functions like snprintf() can clobber
errno even on success.
Since snprintf() is called right before ASSERT_EQ(errno, EINVAL, assert_name),
is it possible that errno gets modified here and causes the assertion to
fail?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260907053847.1115-1-zhaofuyu@vivo.com?part=2
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
2026-09-07 5:38 ` [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
2026-09-07 5:49 ` sashiko-bot
@ 2026-09-07 8:33 ` Jiri Olsa
2026-09-09 14:08 ` Fuyu Zhao
1 sibling, 1 reply; 13+ messages in thread
From: Jiri Olsa @ 2026-09-07 8:33 UTC (permalink / raw)
To: Fuyu Zhao; +Cc: bpf, eddyz87, andrii.nakryiko, alan.maguire, Andrii Nakryiko
On Mon, Sep 07, 2026 at 01:38:46PM +0800, Fuyu Zhao wrote:
SNIP
> static int load_module_btfs(struct bpf_object *obj)
> {
> struct bpf_btf_info info;
> @@ -5825,6 +5842,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)
> @@ -5867,6 +5887,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) {
> @@ -5891,6 +5916,10 @@ static int load_module_btfs(struct bpf_object *obj)
> break;
> }
> obj->btf_module_cnt++;
> +
> + if (obj->btf_module_allowlist &&
IIUC you don't need to check obj->btf_module_allowlist,
because obj->btf_module_allowlist_cnt is -1 by default?
> + obj->btf_module_allowlist_cnt == obj->btf_module_cnt)
> + break;
> }
>
> if (err) {
> @@ -8426,8 +8455,10 @@ 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;
> + const char **mod_allow;
> + int mod_allow_cnt;
> struct bpf_object *obj;
> - int err;
> + int err, i, j;
> char *log_buf;
> size_t log_size;
> __u32 log_level;
> @@ -8470,6 +8501,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 && mod_allow_cnt > 0) || 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);
> + }
do we care if user supplied same name multiple times?
thanks,
jirka
> + }
> +
> obj = bpf_object__new(path, obj_buf, obj_buf_sz, obj_name);
> if (IS_ERR(obj))
> return obj;
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading
2026-09-07 5:38 ` [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
2026-09-07 5:51 ` sashiko-bot
@ 2026-09-07 8:34 ` Jiri Olsa
2026-09-09 14:13 ` Fuyu Zhao
2026-09-08 16:03 ` bot+bpf-ci
2 siblings, 1 reply; 13+ messages in thread
From: Jiri Olsa @ 2026-09-07 8:34 UTC (permalink / raw)
To: Fuyu Zhao; +Cc: bpf, eddyz87, andrii.nakryiko, alan.maguire
On Mon, Sep 07, 2026 at 01:38:47PM +0800, Fuyu Zhao wrote:
SNIP
> +static void btf_module_allowlist_default(void)
> +{
> + struct btf_module_allowlist *skel;
> + int ret;
> + LIBBPF_OPTS(bpf_object_open_opts, opts);
> +
> + 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);
> + skel = NULL;
> +
> + 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_OK(ret, "btf_module_allowlist__load opts");
> +
> + btf_module_allowlist__destroy(skel);
^^^ seems redundant, I don't think we need to test that
SNIP
> +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 (libbpf_get_error(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 (libbpf_get_error(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("default"))
> + btf_module_allowlist_default();
> +
> + if (test__start_subtest("allowlist"))
> + btf_module_allowlist_allow();
> +
> + if (test__start_subtest("skip_unlisted"))
> + btf_module_allowlist_skip();
> +
> + if (test__start_subtest("emptylist"))
> + btf_module_allowlist_empty();
> +
> + if (test__start_subtest("invalid_input"))
> + btf_module_allowlist_invalid();
could you add test that verifies the module's BTF did not get
load because of the allow list setup, something like:
bpf_program__set_attach_target(prog, 0, "bpf_testmod:bpf_fentry_test1")
would fail, or some other way that uses obj->btf_modules
thanks,
jirka
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading
2026-09-07 5:38 ` [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
2026-09-07 5:51 ` sashiko-bot
2026-09-07 8:34 ` Jiri Olsa
@ 2026-09-08 16:03 ` bot+bpf-ci
2026-09-09 14:13 ` Fuyu Zhao
2 siblings, 1 reply; 13+ messages in thread
From: bot+bpf-ci @ 2026-09-08 16:03 UTC (permalink / raw)
To: zhaofuyu, bpf
Cc: eddyz87, andrii.nakryiko, alan.maguire, zhaofuyu, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3836 bytes --]
> 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 000000000000..fb1b9ffb6c7a
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
> @@ -0,0 +1,155 @@
[ ... ]
> +static void btf_module_allowlist_default(void)
> +{
> + struct btf_module_allowlist *skel;
> + int ret;
> + LIBBPF_OPTS(bpf_object_open_opts, opts);
> +
> + 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);
> + skel = NULL;
> +
> + 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_OK(ret, "btf_module_allowlist__load opts");
> +
> + btf_module_allowlist__destroy(skel);
> +}
Jiri Olsa noted in v7 that the second btf_module_allowlist__open_opts(&opts)
call appears redundant. This test section duplicates the same test logic
already covered in the first part of the function. Could this second test
be removed to simplify the test?
[ ... ]
> +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 (libbpf_get_error(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 (libbpf_get_error(module_btf)) {
> + printf("%s:SKIP: bpf_testmod's BTF is not available\n", __func__);
> + btf__free(vmlinux_btf);
> + test__skip();
> + return;
> + }
Andrii Nakryiko requested in v6 not to use libbpf_get_error() as it is
legacy and discouraged. He suggested checking for NULL and then looking at
errno instead. The code still uses libbpf_get_error() here and again below
for the module_btf check. Could these be updated to follow the recommended
pattern?
> +
> + btf__free(module_btf);
> + btf__free(vmlinux_btf);
> +
> + if (test__start_subtest("default"))
> + btf_module_allowlist_default();
> +
> + if (test__start_subtest("allowlist"))
> + btf_module_allowlist_allow();
> +
> + if (test__start_subtest("skip_unlisted"))
> + btf_module_allowlist_skip();
> +
> + if (test__start_subtest("emptylist"))
> + btf_module_allowlist_empty();
> +
> + if (test__start_subtest("invalid_input"))
> + btf_module_allowlist_invalid();
> +}
Jiri Olsa also suggested in v7 adding a test that verifies the module's BTF
did not get loaded because of the allowlist setup. He recommended using
something like bpf_program__set_attach_target(prog, 0,
"bpf_testmod:bpf_fentry_test1") which would fail, or some other way that
uses obj->btf_modules. This would improve test coverage by directly verifying
that module BTF filtering works at the BTF loading level, not just at the
program loading level. Would it be worth adding such a test?
> 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 000000000000..bb3c38bee77d
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/btf_module_allowlist.c
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34245661129
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
2026-09-07 5:49 ` sashiko-bot
@ 2026-09-09 14:08 ` Fuyu Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-09 14:08 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On 9/7/2026 1:49 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] If `calloc` fails when allocating `btf_module_allowlist`, a NULL pointer dereference occurs during cleanup in `bpf_object__close()`.
> --
>
> commit 2653ece25005f3595512126454652c2d89f17f3d
> Author: Fuyu Zhao <zhaofuyu@vivo.com>
>
> libbpf: support selective kernel module BTF loading via bpf_object_open_opts
>
> This commit adds a btf_module_allowlist field to bpf_object_open_opts to
> limit which kernel module BTFs libbpf is allowed to load. When the option is
> unspecified, behavior remains unchanged, while an explicitly empty list
> prevents consulting any module BTFs.
>
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index c036e8a91ed88..d49e48c5c1417 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
> [ ... ]
>> @@ -8563,6 +8610,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 (obj->btf_module_allowlist_cnt > 0) {
>> + obj->btf_module_allowlist = calloc(obj->btf_module_allowlist_cnt,
>> + sizeof(*obj->btf_module_allowlist));
>> + if (!obj->btf_module_allowlist) {
>> + err = -ENOMEM;
>> + goto out;
>
> [Severity: High]
> If this calloc fails, obj->btf_module_allowlist_cnt is left > 0 while
> obj->btf_module_allowlist remains NULL. The goto out path will then call
> bpf_object__close() to clean up the object.
>
Nice catch. You're right, this can leave the object in an invalid state
when calloc() fails. I'll fix it.
>> + }
>> +
>> + for (i = 0; i < obj->btf_module_allowlist_cnt; i++) {
>> + obj->btf_module_allowlist[i] = strdup(mod_allow[i]);
>> + if (!obj->btf_module_allowlist[i]) {
>> + err = -ENOMEM;
>> + goto out;
>> + }
>> + }
>> + }
> [ ... ]
>> @@ -9684,6 +9750,10 @@ void bpf_object__close(struct bpf_object *obj)
>> close(obj->jumptable_maps[i].fd);
>> zfree(&obj->jumptable_maps);
>>
>> + for (j = 0; j < obj->btf_module_allowlist_cnt; j++)
>> + zfree(&obj->btf_module_allowlist[j]);
>
> [Severity: High]
> Will this crash if the initial calloc failed in bpf_object_open()?
>
> Because obj->btf_module_allowlist_cnt is > 0 but obj->btf_module_allowlist
> is NULL, the loop will attempt to access &obj->btf_module_allowlist[j],
> which evaluates to an offset from NULL. When zfree() tries to dereference
> this pointer to free it, it will result in a NULL pointer dereference.
>
> Should obj->btf_module_allowlist_cnt be cleared when calloc fails, or
> should this loop check if obj->btf_module_allowlist is valid before looping?
>
Same issue as above.
>> + zfree(&obj->btf_module_allowlist);
>> +
>> free(obj);
>> }
>>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading
2026-09-07 5:51 ` sashiko-bot
@ 2026-09-09 14:08 ` Fuyu Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-09 14:08 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On 9/7/2026 1:51 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> New issues:
> - [Medium] Flaky Test / errno Clobbering
>
> Pre-existing issues:
> - [High] NULL Pointer Dereference in bpf_object__close() on allocation failure
> --
>
> commit b3d2f975120fb1ef7382fad8983c653ebd2ae39c
> Author: Fuyu Zhao <zhaofuyu@vivo.com>
>
> selftests/bpf: add tests for selective module BTF loading
>
> This commit adds selftests for selective kernel module BTF loading using
> bpf_object_open_opts. It verifies that valid allowlists succeed, module BTFs
> not listed are skipped, and invalid inputs are rejected.
>
>> 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..fb1b9ffb6c7aa
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
>
> [ ... ]
>
>> +static void test_invalid_input(const char **list, int count,
>> + const char *test_name)
>> +{
>> + struct btf_module_allowlist *skel;
>> + char assert_name[64];
>> + 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);
>
> [Severity: High]
> This isn't a bug introduced by this patch, but does passing a negative count
> or encountering an allocation failure here lead to a crash inside libbpf?
>
> When btf_module_allowlist__open_opts() calls into bpf_object_open(), if the
> count is huge (e.g., from passing -1 in the negative_count subtest) or calloc()
> fails with ENOMEM, the array remains NULL:
>
> tools/lib/bpf/libbpf.c:bpf_object_open() {
> ...
> obj->btf_module_allowlist = calloc(obj->btf_module_allowlist_cnt,
> sizeof(*obj->btf_module_allowlist));
> if (!obj->btf_module_allowlist) {
> err = -ENOMEM;
> goto out; /* out calls bpf_object__close(obj) */
> }
> ...
> }
>
> Then bpf_object__close() iterates over the unallocated array:
>
> tools/lib/bpf/libbpf.c:bpf_object__close() {
> ...
> for (j = 0; j < obj->btf_module_allowlist_cnt; j++)
> zfree(&obj->btf_module_allowlist[j]);
> ...
> }
>
> Since obj->btf_module_allowlist is NULL but obj->btf_module_allowlist_cnt is
> greater than 0 (or -1 cast to a large unsigned type), won't this blindly access
> memory and crash?
>
The negative count is rejected by the validation check in patch 1/2:
if ((!mod_allow && mod_allow_cnt > 0) || mod_allow_cnt < 0). Therefore,
the negative count case you mentioned won't occur.
The calloc failure is the same issue as in patch 1/2. I'll fix it.
>> + 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(errno, EINVAL, assert_name);
>
> [Severity: Medium]
> Can this cause test flakiness?
>
> According to POSIX, standard library functions like snprintf() can clobber
> errno even on success.
>
> Since snprintf() is called right before ASSERT_EQ(errno, EINVAL, assert_name),
> is it possible that errno gets modified here and causes the assertion to
> fail?
>
Nice catch. There is indeed an errno clobbering issue here. I'll fix it.
Thanks,
Fuyu
>> +}
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
2026-09-07 8:33 ` Jiri Olsa
@ 2026-09-09 14:08 ` Fuyu Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-09 14:08 UTC (permalink / raw)
To: Jiri Olsa; +Cc: bpf, eddyz87, andrii.nakryiko, alan.maguire, Andrii Nakryiko
On 9/7/2026 4:33 PM, Jiri Olsa wrote:
> On Mon, Sep 07, 2026 at 01:38:46PM +0800, Fuyu Zhao wrote:
>
> SNIP
>
>> static int load_module_btfs(struct bpf_object *obj)
>> {
>> struct bpf_btf_info info;
>> @@ -5825,6 +5842,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)
>> @@ -5867,6 +5887,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) {
>> @@ -5891,6 +5916,10 @@ static int load_module_btfs(struct bpf_object *obj)
>> break;
>> }
>> obj->btf_module_cnt++;
>> +
>> + if (obj->btf_module_allowlist &&
>
> IIUC you don't need to check obj->btf_module_allowlist,
> because obj->btf_module_allowlist_cnt is -1 by default?
>
Yes, that's correct. I'll remove the
obj->btf_module_allowlist check to keep the code simpler.
>> + obj->btf_module_allowlist_cnt == obj->btf_module_cnt)
>> + break;
>> }
>>
>> if (err) {
>> @@ -8426,8 +8455,10 @@ 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;
>> + const char **mod_allow;
>> + int mod_allow_cnt;
>> struct bpf_object *obj;
>> - int err;
>> + int err, i, j;
>> char *log_buf;
>> size_t log_size;
>> __u32 log_level;
>> @@ -8470,6 +8501,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 && mod_allow_cnt > 0) || 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);
>> + }
>
> do we care if user supplied same name multiple times?
>
Yes, we care about duplicate names. We reject them so that each entry
in the allowlist represents a unique module. This allows the
load_module_btfs() loop to exit early once all requested module BTFs
have been loaded.
> thanks,
> jirka
>
>> + }
>> +
>> obj = bpf_object__new(path, obj_buf, obj_buf_sz, obj_name);
>> if (IS_ERR(obj))
>> return obj;
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading
2026-09-07 8:34 ` Jiri Olsa
@ 2026-09-09 14:13 ` Fuyu Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-09 14:13 UTC (permalink / raw)
To: Jiri Olsa; +Cc: bpf, eddyz87, andrii.nakryiko, alan.maguire
On 9/7/2026 4:34 PM, Jiri Olsa wrote:
> On Mon, Sep 07, 2026 at 01:38:47PM +0800, Fuyu Zhao wrote:
>
> SNIP
>
>> +static void btf_module_allowlist_default(void)
>> +{
>> + struct btf_module_allowlist *skel;
>> + int ret;
>> + LIBBPF_OPTS(bpf_object_open_opts, opts);
>> +
>> + 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);
>> + skel = NULL;
>> +
>> + 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_OK(ret, "btf_module_allowlist__load opts");
>> +
>> + btf_module_allowlist__destroy(skel);
>
> ^^^ seems redundant, I don't think we need to test that
>
Agreed, the second case is redundant. I originally included it to
explicitly cover the default values of the new fields when opts is
provided without setting them. I'll take another look at simplifying
this test.
> SNIP
>
>> +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 (libbpf_get_error(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 (libbpf_get_error(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("default"))
>> + btf_module_allowlist_default();
>> +
>> + if (test__start_subtest("allowlist"))
>> + btf_module_allowlist_allow();
>> +
>> + if (test__start_subtest("skip_unlisted"))
>> + btf_module_allowlist_skip();
>> +
>> + if (test__start_subtest("emptylist"))
>> + btf_module_allowlist_empty();
>> +
>> + if (test__start_subtest("invalid_input"))
>> + btf_module_allowlist_invalid();
>
> could you add test that verifies the module's BTF did not get
> load because of the allow list setup, something like:
>
> bpf_program__set_attach_target(prog, 0, "bpf_testmod:bpf_fentry_test1")
>
> would fail, or some other way that uses obj->btf_modules
>
Sure, I'll add a test using bpf_program__set_attach_target(prog, 0,
"bpf_testmod:bpf_fentry_test1") to verify that the allow list correctly
prevents the module BTF from being loaded. This makes the verification
more clear and explicit.
Thanks,
Fuyu
> thanks,
> jirka
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading
2026-09-08 16:03 ` bot+bpf-ci
@ 2026-09-09 14:13 ` Fuyu Zhao
0 siblings, 0 replies; 13+ messages in thread
From: Fuyu Zhao @ 2026-09-09 14:13 UTC (permalink / raw)
To: bot+bpf-ci, bpf
Cc: eddyz87, andrii.nakryiko, alan.maguire, ast, andrii, daniel,
martin.lau, yonghong.song, mason, ihor.solodrai
On 9/9/2026 12:03 AM, bot+bpf-ci@kernel.org wrote:
>> 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 000000000000..fb1b9ffb6c7a
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_allowlist.c
>> @@ -0,0 +1,155 @@
>
> [ ... ]
>
>> +static void btf_module_allowlist_default(void)
>> +{
>> + struct btf_module_allowlist *skel;
>> + int ret;
>> + LIBBPF_OPTS(bpf_object_open_opts, opts);
>> +
>> + 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);
>> + skel = NULL;
>> +
>> + 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_OK(ret, "btf_module_allowlist__load opts");
>> +
>> + btf_module_allowlist__destroy(skel);
>> +}
>
> Jiri Olsa noted in v7 that the second btf_module_allowlist__open_opts(&opts)
> call appears redundant. This test section duplicates the same test logic
> already covered in the first part of the function. Could this second test
> be removed to simplify the test?
>
Agreed, the second case is redundant, so I'll remove it.
> [ ... ]
>
>> +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 (libbpf_get_error(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 (libbpf_get_error(module_btf)) {
>> + printf("%s:SKIP: bpf_testmod's BTF is not available\n", __func__);
>> + btf__free(vmlinux_btf);
>> + test__skip();
>> + return;
>> + }
>
> Andrii Nakryiko requested in v6 not to use libbpf_get_error() as it is
> legacy and discouraged. He suggested checking for NULL and then looking at
> errno instead. The code still uses libbpf_get_error() here and again below
> for the module_btf check. Could these be updated to follow the recommended
> pattern?
>
Sorry, this was my oversight. I updated some of the libbpf_get_error()
checks but overlooked these remaining ones. I'll fix them as suggested.
>> +
>> + btf__free(module_btf);
>> + btf__free(vmlinux_btf);
>> +
>> + if (test__start_subtest("default"))
>> + btf_module_allowlist_default();
>> +
>> + if (test__start_subtest("allowlist"))
>> + btf_module_allowlist_allow();
>> +
>> + if (test__start_subtest("skip_unlisted"))
>> + btf_module_allowlist_skip();
>> +
>> + if (test__start_subtest("emptylist"))
>> + btf_module_allowlist_empty();
>> +
>> + if (test__start_subtest("invalid_input"))
>> + btf_module_allowlist_invalid();
>> +}
>
> Jiri Olsa also suggested in v7 adding a test that verifies the module's BTF
> did not get loaded because of the allowlist setup. He recommended using
> something like bpf_program__set_attach_target(prog, 0,
> "bpf_testmod:bpf_fentry_test1") which would fail, or some other way that
> uses obj->btf_modules. This would improve test coverage by directly verifying
> that module BTF filtering works at the BTF loading level, not just at the
> program loading level. Would it be worth adding such a test?
>
Sure, I'll add a test for this.
Thanks,
Fuyu
>> 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 000000000000..bb3c38bee77d
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/btf_module_allowlist.c
>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34245661129
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-09-09 14:13 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-07 5:38 [PATCH bpf-next v7 0/2] libbpf: improve BPF load performance by selectively loading module BTFs Fuyu Zhao
2026-09-07 5:38 ` [PATCH bpf-next v7 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
2026-09-07 5:49 ` sashiko-bot
2026-09-09 14:08 ` Fuyu Zhao
2026-09-07 8:33 ` Jiri Olsa
2026-09-09 14:08 ` Fuyu Zhao
2026-09-07 5:38 ` [PATCH bpf-next v7 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
2026-09-07 5:51 ` sashiko-bot
2026-09-09 14:08 ` Fuyu Zhao
2026-09-07 8:34 ` Jiri Olsa
2026-09-09 14:13 ` Fuyu Zhao
2026-09-08 16:03 ` bot+bpf-ci
2026-09-09 14:13 ` Fuyu Zhao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox