From: sashiko-bot@kernel.org
To: "Jay Wang" <wanjay@amazon.com>,
bpf@vger.kernel.org, "Alexei Starovoitov" <ast@kernel.org>,
"Daniel Borkmann" <daniel@iogearbox.net>,
"Andrii Nakryiko" <andrii@kernel.org>,
"Eduard Zingerman" <eddyz87@gmail.com>,
"Kumar Kartikeya Dwivedi" <memxor@gmail.com>
Cc: "Ihor Solodrai" <ihor.solodrai@linux.dev>,
"Alan Maguire" <almagui@kernel.org>,
"Martin KaFai Lau" <martin.lau@linux.dev>,
"Yonghong Song" <yonghong.song@linux.dev>,
"Jiri Olsa" <jolsa@kernel.org>,
"Steven Rostedt" <rostedt@goodmis.org>,
"Masami Hiramatsu" <mhiramat@kernel.org>,
"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
linux-trace-kernel@vger.kernel.org,
"Nathan Chancellor" <nathan@kernel.org>,
"Nicolas Schier" <nsc@kernel.org>,
linux-kbuild@vger.kernel.org, "Arnd Bergmann" <arnd@arndb.de>,
x86@kernel.org, "Hazem Mohamed Abuelfotoh" <abuehaze@amazon.com>,
"Bjoern Doebel" <doebel@amazon.de>,
"Martin Pohlack" <mpohlack@amazon.de>,
jay.wang.upstream@gmail.com
Subject: Re: [PATCH bpf-next 11/13] bpf: keep module BTF until the vmlinux BTF is parsed
Date: Sat, 31 Oct 2026 13:37:45 +0000 [thread overview]
Message-ID: <sashiko-outbox-191981@kernel.org> (raw)
In-Reply-To: <20261031130959.23472-12-wanjay@amazon.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] bpf: NULL deref in btf_parse_kept_modules() after inline sysfs failure
- [High] bpf: UAF of btf_mod->data in btf_free_rcu() after kept module unparse
- [High] bpf: premature btf_idr publication in btf_parse_kept_modules() causes UAF
- [Medium] bpf: btf_parse_kept_modules() drops kept inline BTF on -ENOMEM
- [Medium] bpf: drop kept module BTFs in btf_drop_all_deferred() on vmlinux BTF failure
--
commit c77c66110b385c53a4bc18e94bea01ef20eb7416
Author: Jay Wang <wanjay@amazon.com>
bpf: keep module BTF until the vmlinux BTF is parsed
With CONFIG_DEBUG_INFO_BTF_LAZY enabled, this commit keeps in-tree module
BTF and inline BTF unparsed when a module loads before vmlinux BTF is
parsed, exposing their sysfs files immediately and parsing the kept module
BTFs under btf_vmlinux_lock when vmlinux BTF is first parsed.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index a5b1cd63a1e43..631f5a32ec174 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> @@ -9395,6 +9398,149 @@ static DEFINE_MUTEX(btf_module_mutex);
> static void purge_cand_cache(struct btf *btf);
> static void btf_drop_deferred_regs(struct module *owner);
>
> +/* /sys/kernel/btf/<module> and <module>.inline */
> +static void btf_module_add_files(struct btf_module *btf_mod, const char *name,
> + void *data, u32 data_size)
> +{
> + char inline_name[MODULE_NAME_LEN + sizeof(".inline")];
> + struct bin_attribute *attr;
> +
> + attr = sysfs_btf_add(name, data, data_size, false, NULL);
> + if (IS_ERR(attr))
> + return;
> + btf_mod->sysfs_attr = attr;
> +
> + if (!btf_mod->btf_inline_data)
> + return;
> + snprintf(inline_name, sizeof(inline_name), "%s.inline", name);
> + attr = sysfs_btf_add(inline_name, btf_mod->btf_inline_data,
> + btf_mod->btf_inline_data_size, false, NULL);
> + if (IS_ERR(attr)) {
> + kvfree(btf_mod->btf_inline_data);
> + btf_mod->btf_inline_data = NULL;
> + return;
> + }
[Severity: High]
Can this trigger a NULL pointer dereference when btf_parse_kept_modules()
later parses this module?
In btf_module_keep(), btf_mod->btf_inline_data_size is set to a non-zero
inline_data_size before calling btf_module_add_files(). If sysfs_btf_add()
fails for the .inline file here, btf_mod->btf_inline_data is freed and set
to NULL, but btf_mod->btf_inline_data_size remains non-zero.
Later, btf_parse_kept_modules() passes btf_mod->btf_inline_data (NULL) and
btf_mod->btf_inline_data_size (non-zero) to btf_parse_module():
if (inline_data_size) {
inline_btf = btf_parse_split(env, module_name, inline_data,
inline_data_size, btf);
which calls btf_parse_split() -> btf_parse_hdr() with btf->data == NULL and
btf->data_size > 0, dereferencing hdr = btf->data.
Should btf_mod->btf_inline_data_size also be reset to 0 here (and if the
first sysfs_btf_add() call fails above, should btf_mod->btf_inline_data also
be freed and cleared)?
> + btf_mod->sysfs_inline_attr = attr;
> +}
[ ... ]
> +/* Undo btf_parse_kept_modules() of a round that is not published */
> +static void btf_unparse_kept_modules(void)
> +{
> + struct btf_module *btf_mod;
> +
> + lockdep_assert_held(&btf_module_mutex);
> + list_for_each_entry(btf_mod, &btf_modules, list) {
> + if (!btf_mod->data || !btf_mod->btf)
> + continue;
> + btf_mod->btf->data = NULL; /* the kept copy */
> + btf_put(btf_mod->btf);
> + btf_mod->btf = NULL;
> + }
> +}
[Severity: High]
Could btf_free_rcu() read freed btf_mod->data memory after
btf_unparse_kept_modules() runs?
In btf_apply_deferred(), after btf_parse_kept_modules() sets up
btf_mod->btf in place over btf_mod->data, an earlier deferred
BTF_DEFER_ST_OPS registration can populate btf_mod->btf->struct_ops_tab via
__register_bpf_struct_ops() -> btf_add_struct_ops() ->
bpf_struct_ops_desc_init(), storing st_ops_desc->type = btf_type_by_id()
which points directly into btf_mod->data.
If a subsequent deferred registration in btf_apply_deferred() fails with
-ENOMEM, btf_unparse_kept_modules() sets btf_mod->btf->data = NULL, schedules
asynchronous destruction of btf_mod->btf via btf_put() -> call_rcu(), and
resets btf_mod->btf = NULL.
If the module unloads (MODULE_STATE_GOING) before that RCU grace period
finishes, btf_module_notify() sees !btf_mod->btf and calls
btf_module_drop_kept(btf_mod), which immediately frees btf_mod->data with
kvfree(). When btf_free_rcu() -> btf_free() -> btf_free_struct_ops_tab() ->
bpf_struct_ops_desc_release() later executes:
arg_info = st_ops_desc->arg_info;
for (i = 0; i < btf_type_vlen(st_ops_desc->type); i++)
kfree(arg_info[i].info);
doesn't btf_type_vlen(st_ops_desc->type) dereference the already freed
btf_mod->data buffer?
> +
> +/*
> + * Called under btf_vmlinux_lock by the task that parses the vmlinux BTF,
> + * before that is published. Parses the kept module BTFs in place, oldest
> + * first so that ids follow the load order as without
> + * CONFIG_DEBUG_INFO_BTF_LAZY; in-tree module BTF and its inline BTF need no
> + * relocation, so sysfs serves the same bytes before and after. A module
> + * whose BTF fails validation cannot be refused any more: it stays loaded
> + * without BTF, and its files go away; an invalid inline BTF loses its file.
> + * Returns -ENOMEM, with this round's module BTFs freed again, if memory ran
> + * out.
> + */
> +static int btf_parse_kept_modules(void)
> +{
> + struct btf_module *btf_mod, *tmp;
> + void *inline_data;
> + struct btf *btf;
> + int err;
> +
> + lockdep_assert_held(&btf_vmlinux_lock);
> + guard(mutex)(&btf_module_mutex);
> + list_for_each_entry_safe_reverse(btf_mod, tmp, &btf_modules, list) {
> + if (btf_mod->btf || !btf_mod->data)
> + continue;
> + btf = btf_parse_module(btf_mod->module->name, btf_mod->data,
> + btf_mod->data_size, NULL, 0,
> + btf_mod->btf_inline_data,
> + btf_mod->btf_inline_data_size, &inline_data);
> + if (!IS_ERR(btf)) {
> + err = btf_alloc_id(btf);
> + if (err) {
> + btf->data = NULL; /* the kept copy */
> + btf_free(btf);
> + btf = ERR_PTR(err);
> + }
> + }
[Severity: High]
Is it safe to publish btf into btf_idr via btf_alloc_id(btf) before the rest
of btf_parse_kept_modules() and btf_apply_deferred() have succeeded?
In btf_get_fd_by_id(), the return value of bpf_get_btf_vmlinux() is ignored:
if (IS_ENABLED(CONFIG_DEBUG_INFO_BTF_LAZY))
bpf_get_btf_vmlinux();
rcu_read_lock();
btf = idr_find(&btf_idr, id);
if (!btf || !refcount_inc_not_zero(&btf->refcnt))
If task A's bpf_get_btf_vmlinux() fails with -ENOMEM, task A still proceeds
to idr_find(&btf_idr, id) while task B is inside btf_apply_deferred() ->
btf_parse_kept_modules(). Task A (or a deferred struct_ops ->init() callback
calling bpf_find_btf_id()) can grab a reference to btf_mod->btf from btf_idr,
raising refcnt above 1.
If task B then hits -ENOMEM on a later module or deferred registration,
btf_unparse_kept_modules() sets btf_mod->btf->data = NULL and calls
btf_put(btf_mod->btf), and bpf_get_btf_vmlinux() frees the unpublished
vmlinux BTF via btf_put(). Because btf_mod->btf->refcnt > 1, btf_put() does
not free btf_mod->btf or remove it from btf_idr, leaving a live BTF object
with btf->data == NULL (which crashes in btf_get_info_by_fd() on
copy_to_user(ubtf, btf->data, btf_copy)) and a dangling btf->base_btf
pointer to the freed vmlinux BTF.
> + if (btf == ERR_PTR(-ENOMEM)) {
> + btf_unparse_kept_modules();
> + return -ENOMEM;
> + }
> + if (IS_ERR(btf)) {
> + pr_warn("failed to validate module [%s] BTF: %ld\n",
> + btf_mod->module->name, PTR_ERR(btf));
> + btf_module_drop_kept(btf_mod);
> + continue;
> + }
> + btf_mod->btf = btf;
> + if (btf_mod->btf_inline_data && !inline_data) {
> + if (btf_mod->sysfs_inline_attr)
> + sysfs_btf_remove(btf_mod->sysfs_inline_attr);
> + btf_mod->sysfs_inline_attr = NULL;
> + kvfree(btf_mod->btf_inline_data);
> + btf_mod->btf_inline_data = NULL;
> + btf_mod->btf_inline_data_size = 0;
> + }
[Severity: Medium]
If parsing a kept module's inline BTF fails with -ENOMEM inside
btf_parse_module(), doesn't this permanently drop the module's inline BTF
and sysfs file instead of rolling back the round?
In btf_parse_module(), an error from btf_parse_split() on inline_btf (such
as ERR_PTR(-ENOMEM)) is logged and cleared to inline_btf = NULL, returning
the main module btf with inline_data == NULL. btf_parse_kept_modules() then
treats !inline_data as a validation failure and removes
btf_mod->sysfs_inline_attr and frees btf_mod->btf_inline_data, even if the
round fails with -ENOMEM and is retried later.
> + }
> + return 0;
> +}
[ ... ]
> @@ -11206,8 +11349,13 @@ static int btf_apply_deferred(void)
> break;
> }
> module_put(reg->owner);
> - if (err == -ENOMEM)
> + if (err == -ENOMEM) {
> +#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
> + guard(mutex)(&btf_module_mutex);
> + btf_unparse_kept_modules();
> +#endif
> return err;
> + }
> if (err)
> pr_warn("failed to apply deferred BTF registration for %s: %d\n",
> reg->owner ? reg->owner->name : "vmlinux", err);
[Severity: Medium]
What happens to kept module BTFs if btf_parse_vmlinux() in
bpf_get_btf_vmlinux() fails with a non-ENOMEM error (or btf_apply_deferred()
were to return a non-ENOMEM error)?
In bpf_get_btf_vmlinux(), when btf is a non-ENOMEM error pointer,
btf_apply_deferred() is not run (or fails) and btf_drop_all_deferred() is
called before publishing the error pointer to btf_vmlinux. However,
btf_drop_all_deferred() only frees entries in btf_deferred_regs and does not
call btf_module_drop_kept() on kept entries in btf_modules, leaving their
unparsed data buffers and /sys/kernel/btf/<module> sysfs files around until
module unload. Should btf_drop_all_deferred() also drop kept module entries?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261031130959.23472-1-wanjay@amazon.com?part=11
next prev parent reply other threads:[~2026-10-31 13:37 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 13:09 [PATCH bpf-next 00/13] bpf: keep the vmlinux BTF compressed until first use (CONFIG_DEBUG_INFO_BTF_LAZY) to save ~6 MB memory Jay Wang
2026-10-31 13:09 ` [PATCH bpf-next 01/13] bpf: match program context types by name without vmlinux BTF Jay Wang
2026-10-31 13:43 ` bot+bpf-ci
2026-10-31 13:09 ` [PATCH bpf-next 02/13] bpf: fetch the vmlinux BTF where kernel types enter a program Jay Wang
2026-10-31 13:42 ` sashiko-bot
2026-10-31 13:55 ` bot+bpf-ci
2026-10-31 13:09 ` [PATCH bpf-next 03/13] bpf: check CAP_SYS_ADMIN before looking up bpffs delegation names Jay Wang
2026-10-31 13:43 ` bot+bpf-ci
2026-10-31 17:03 ` Alexei Starovoitov
2026-10-31 13:09 ` [PATCH bpf-next 04/13] bpf: move bpf_get_btf_vmlinux() to btf.c Jay Wang
2026-10-31 13:43 ` bot+bpf-ci
2026-10-31 13:09 ` [PATCH bpf-next 05/13] bpf: let btf_parse_module() adopt the caller's copy of the module BTF Jay Wang
2026-10-31 13:09 ` [PATCH bpf-next 06/13] bpf: look up the struct_ops common value type in the given BTF Jay Wang
2026-10-31 13:09 ` [PATCH bpf-next 07/13] bpf: parse the vmlinux BTF before taking cand_cache_mutex Jay Wang
2026-10-31 13:43 ` bot+bpf-ci
2026-10-31 13:09 ` [PATCH bpf-next 08/13] tracing: parse the vmlinux BTF when BTF-typed output is enabled Jay Wang
2026-10-31 13:37 ` sashiko-bot
2026-10-31 13:43 ` bot+bpf-ci
2026-10-31 13:09 ` [PATCH bpf-next 09/13] bpf: keep the vmlinux BTF zstd-compressed in the kernel image Jay Wang
2026-10-31 13:33 ` sashiko-bot
2026-10-31 13:55 ` bot+bpf-ci
2026-10-31 13:09 ` [PATCH bpf-next 10/13] bpf: defer kfunc and struct_ops registrations until first BTF use Jay Wang
2026-10-31 13:38 ` sashiko-bot
2026-10-31 13:55 ` bot+bpf-ci
2026-10-31 17:07 ` Alexei Starovoitov
2026-10-31 13:09 ` [PATCH bpf-next 11/13] bpf: keep module BTF until the vmlinux BTF is parsed Jay Wang
2026-10-31 13:37 ` sashiko-bot [this message]
2026-10-31 13:55 ` bot+bpf-ci
2026-10-31 17:10 ` Alexei Starovoitov
2026-10-31 13:09 ` [PATCH bpf-next 12/13] selftests/bpf: keep ksyms and linked_vars working without __start_BTF Jay Wang
2026-10-31 13:09 ` [PATCH bpf-next 13/13] Documentation/bpf: describe CONFIG_DEBUG_INFO_BTF_LAZY Jay Wang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-191981@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=abuehaze@amazon.com \
--cc=almagui@kernel.org \
--cc=andrii@kernel.org \
--cc=arnd@arndb.de \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=doebel@amazon.de \
--cc=eddyz87@gmail.com \
--cc=ihor.solodrai@linux.dev \
--cc=jay.wang.upstream@gmail.com \
--cc=jolsa@kernel.org \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=mathieu.desnoyers@efficios.com \
--cc=memxor@gmail.com \
--cc=mhiramat@kernel.org \
--cc=mpohlack@amazon.de \
--cc=nathan@kernel.org \
--cc=nsc@kernel.org \
--cc=rostedt@goodmis.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=wanjay@amazon.com \
--cc=x86@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox