Linux kbuild/kconfig development
 help / color / mirror / Atom feed
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

  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