Linux Trace Kernel
 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 10/13] bpf: defer kfunc and struct_ops registrations until first BTF use
Date: Sat, 31 Oct 2026 13:38:17 +0000	[thread overview]
Message-ID: <sashiko-outbox-191982@kernel.org> (raw)
In-Reply-To: <20261031130959.23472-11-wanjay@amazon.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] bpf: propagate st_ops->init() error in bpf_struct_ops_desc_init()
- [Medium] bpf: premature btf_idr publication of vmlinux BTF before btf_apply_deferred()
- [Medium] bpf: missing bpf_get_btf_vmlinux() in cgroup_bpf_prog_query()
--

commit ee51b32030c948007ccf4ba129182f790083514d
Author: Jay Wang <wanjay@amazon.com>

bpf: defer kfunc and struct_ops registrations until first BTF use

When CONFIG_DEBUG_INFO_BTF_LAZY is enabled, this commit queues kfunc set,
dtor kfunc, and struct_ops registrations made before vmlinux BTF is parsed
and replays them synchronously inside bpf_get_btf_vmlinux() before publishing
btf_vmlinux. It also triggers vmlinux BTF parsing from btf_get_fd_by_id() and
BPF_BTF_GET_NEXT_ID so kernel BTF IDs are allocated before IDR lookups.

> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index a7bb780743fb2..a5b1cd63a1e43 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -6930,17 +6930,54 @@ struct btf *btf_parse_vmlinux(void)
>  }
>  
>  static DEFINE_MUTEX(btf_vmlinux_lock);
> +/* CONFIG_DEBUG_INFO_BTF_LAZY: the task applying deferred work, and its BTF */
> +static struct task_struct *btf_vmlinux_builder;
> +static struct btf *btf_vmlinux_new;
> +static int btf_apply_deferred(void);
> +static void btf_drop_all_deferred(int err);
> +
> +static bool btf_vmlinux_building(void)
> +{
> +	return in_task() && READ_ONCE(btf_vmlinux_builder) == current;
> +}
>  
>  struct btf *bpf_get_btf_vmlinux(void)
>  {
>  	/* Pairs with the smp_store_release() on the parse path below. */
>  	struct btf *btf = smp_load_acquire(&btf_vmlinux);
>  
> -	if (!btf && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {
> -		mutex_lock(&btf_vmlinux_lock);
> -		btf = btf_vmlinux;
> -		if (!btf) {
> -			btf = btf_parse_vmlinux();
> +	/* with CONFIG_DEBUG_INFO_BTF_LAZY any call may be the one that parses */
> +	if (IS_ENABLED(CONFIG_DEBUG_INFO_BTF_LAZY))
> +		might_sleep();
> +	if (btf || !IS_ENABLED(CONFIG_DEBUG_INFO_BTF))
> +		return btf;
> +	/* the registrations btf_apply_deferred() applies see the new BTF */
> +	if (btf_vmlinux_building())
> +		return btf_vmlinux_new;
> +
> +	mutex_lock(&btf_vmlinux_lock);
> +	btf = btf_vmlinux;
> +	if (!btf) {
> +		btf = btf_parse_vmlinux();
> +		if (IS_ENABLED(CONFIG_DEBUG_INFO_BTF_LAZY) && !IS_ERR(btf)) {
> +			int err;
> +
> +			btf_vmlinux_new = btf;
> +			WRITE_ONCE(btf_vmlinux_builder, current);
> +			err = btf_apply_deferred();
> +			WRITE_ONCE(btf_vmlinux_builder, NULL);
> +			btf_vmlinux_new = NULL;
> +			if (err) {
> +				/* nothing saw it: drop it with what was applied to it */
> +				bpf_ctx_convert.t = NULL;
> +				btf_put(btf);
> +				btf = ERR_PTR(err);
> +			}
> +		}
> +		/* a lazy parse that ran out of memory is retried by the next caller */
> +		if (!IS_ENABLED(CONFIG_DEBUG_INFO_BTF_LAZY) || btf != ERR_PTR(-ENOMEM)) {
> +			if (IS_ERR(btf))
> +				btf_drop_all_deferred(PTR_ERR(btf));
>  			/*
>  			 * Order the parsed BTF contents and the globals the
>  			 * parse populated (e.g. bpf_ctx_convert.t) before
> @@ -6949,8 +6986,8 @@ struct btf *bpf_get_btf_vmlinux(void)
>  			 */
>  			smp_store_release(&btf_vmlinux, btf);
>  		}
> -		mutex_unlock(&btf_vmlinux_lock);
>  	}
> +	mutex_unlock(&btf_vmlinux_lock);
>  	return btf;
>  }
>  
> @@ -9302,6 +9339,10 @@ int btf_get_fd_by_id(u32 id)
>  	struct btf *btf;
>  	int fd;
>  
> +	/* kernel BTFs get their ids while parsed, before they are published */
> +	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))

[Severity: Medium]
Can a concurrent caller of btf_get_fd_by_id() or __sys_bpf() (for
BPF_BTF_GET_NEXT_ID) observe and pin btf_vmlinux_new via btf_idr while
btf_apply_deferred() is still mutating it or rolling it back on -ENOMEM?

In kernel/bpf/btf.c, btf_parse_vmlinux() allocates the IDR entry before
btf_apply_deferred() runs:

btf_parse_vmlinux()
	err = btf_alloc_id(btf);

Both btf_get_fd_by_id() and the BPF_BTF_GET_NEXT_ID case in __sys_bpf()
call bpf_get_btf_vmlinux() and ignore its return value before querying
btf_idr without holding btf_vmlinux_lock.

If task A calls btf_get_fd_by_id() and its bpf_get_btf_vmlinux() attempt
fails with ERR_PTR(-ENOMEM) (for example, if task A is killed or hits a
memory limit during vmalloc()), bpf_get_btf_vmlinux() leaves btf_vmlinux
as NULL and releases btf_vmlinux_lock. Because btf_get_fd_by_id() ignores
the error, task A proceeds to idr_find(&btf_idr, id).

If task B concurrently acquires btf_vmlinux_lock, runs btf_parse_vmlinux()
(publishing btf_vmlinux_new into btf_idr via btf_alloc_id()), and enters
btf_apply_deferred(), task A can find task B's unpublished btf_vmlinux_new
in btf_idr and increment btf->refcnt from 1 to 2 via
refcount_inc_not_zero().

This exposes btf_vmlinux_new to userspace while btf_apply_deferred() is
still mutating kfunc_set_tab, dtor_kfunc_tab, and struct_ops_tab via
krealloc() and sort(). Furthermore, if task B's btf_apply_deferred() then
fails with -ENOMEM, the rollback path in bpf_get_btf_vmlinux() assumes
nothing else saw btf:

	if (err) {
		/* nothing saw it: drop it with what was applied to it */
		bpf_ctx_convert.t = NULL;
		btf_put(btf);
		btf = ERR_PTR(err);
	}

Because task A holds an extra reference, btf_put(btf) will not drop
btf->refcnt to zero or call btf_free_id(btf), leaving the aborted vmlinux
BTF in btf_idr while a subsequent caller parses and allocates a second
vmlinux BTF.

[ ... ]

> @@ -11009,6 +11131,9 @@ int __register_bpf_struct_ops(struct bpf_struct_ops *st_ops)
>  	struct btf *btf;
>  	int err = 0;
>  
> +	if (btf_defer_reg(st_ops->owner, BTF_DEFER_ST_OPS, 0, st_ops, 0, &err))
> +		return err;
> +
>  	btf = btf_get_module_btf(st_ops->owner);
>  	if (!btf)
>  		return check_btf_kconfigs(st_ops->owner, "struct_ops");

[Severity: Medium]
When __register_bpf_struct_ops() is deferred until the first
bpf_get_btf_vmlinux() call, could BPF_PROG_QUERY with attach_type
BPF_STRUCT_OPS fail with -ENOENT if invoked before vmlinux BTF is parsed?

Normally, built-in cgroup struct_ops (such as bpf_tcp_ops registered in
__bpf_tcp_ops_init()) are registered during initcalls via:

__register_bpf_struct_ops()
	btf_add_struct_ops()
		cgroup_bpf_struct_ops_register()

which populates cgroup_struct_ops[cgroup_atype].type_id.

With CONFIG_DEBUG_INFO_BTF_LAZY enabled, btf_defer_reg() defers
__register_bpf_struct_ops(), leaving cgroup_struct_ops[].type_id as 0 until
bpf_get_btf_vmlinux() runs btf_apply_deferred().

If a user reads the struct_ops type_id from /sys/kernel/btf/vmlinux (which
decompresses raw BTF via btf_vmlinux_data() without calling
bpf_get_btf_vmlinux()) and invokes BPF_PROG_QUERY with attach_type
BPF_STRUCT_OPS, execution reaches kernel/bpf/cgroup.c:

bpf_prog_query()
	cgroup_bpf_prog_query()
		cgroup_bpf_query()
			__cgroup_bpf_query()

In __cgroup_bpf_query():

	if (type == BPF_STRUCT_OPS) {
		u32 type_id = attr->query.type_id;

		atype = find_atype_by_struct_ops_id(type_id);
		if (atype == CGROUP_BPF_ATTACH_TYPE_INVALID)
			return -ENOENT;

Because neither cgroup_bpf_prog_query() nor __cgroup_bpf_query() calls
bpf_get_btf_vmlinux(), the deferred struct_ops registrations have not been
applied yet, so find_atype_by_struct_ops_id() returns
CGROUP_BPF_ATTACH_TYPE_INVALID and the query fails with -ENOENT instead of
succeeding with prog_cnt = 0.

> @@ -11034,6 +11159,93 @@ int __register_bpf_struct_ops(struct bpf_struct_ops *st_ops)
>  EXPORT_SYMBOL_GPL(__register_bpf_struct_ops);
>  #endif
>  
> +static void btf_free_deferred_reg(struct btf_deferred_reg *reg)
> +{
> +	list_del(&reg->list);
> +	if (reg->kind != BTF_DEFER_ST_OPS)
> +		kfree(reg->data);
> +	kfree(reg);
> +}
> +
> +/*
> + * Called under btf_vmlinux_lock, by the task that parsed the vmlinux BTF and
> + * before it publishes it, so that nothing sees the BTF without its kfuncs
> + * and struct_ops.  bpf_get_btf_vmlinux() returns the new BTF to
> + * this task, so the registrations are applied as if they were made now, and
> + * any they make in turn (a struct_ops ->init() may register kfuncs) too.
> + * They run in whichever task needs the BTF first, with the locks it holds:
> + * they must not load modules or take locks that users of the BTF hold.
> + *
> + * Returns -ENOMEM if memory ran out, which may be only because that task is
> + * being killed: the caller then drops the new BTF with whatever was applied
> + * to it, and the next caller starts over with the queue intact, as for the
> + * vmlinux BTF itself; a struct_ops ->init() then runs again with the BTF of
> + * that round.  Any other error of a registration is only logged.
> + */
> +static int btf_apply_deferred(void)
> +{
> +	struct btf_deferred_reg *reg, *tmp;
> +	int err;
> +
> +	list_for_each_entry(reg, &btf_deferred_regs, list) {
> +		/* a going module's exit() runs before GOING drops its entries */
> +		if (!try_module_get(reg->owner))
> +			continue;
> +		err = 0;
> +		switch (reg->kind) {
> +		case BTF_DEFER_KFUNCS:
> +			err = __register_btf_kfunc_id_set(reg->hook, reg->data);
> +			break;
> +		case BTF_DEFER_DTORS:
> +			err = register_btf_id_dtor_kfuncs(reg->data, reg->cnt, reg->owner);
> +			break;
> +		case BTF_DEFER_ST_OPS:
> +#if defined(CONFIG_BPF_JIT)
> +			err = __register_bpf_struct_ops(reg->data);
> +#endif
> +			break;
> +		}
> +		module_put(reg->owner);
> +		if (err == -ENOMEM)
> +			return err;

[Severity: Medium]
What happens if a struct_ops ->init() callback fails with -ENOMEM while
registering kfuncs during btf_apply_deferred()?

In-tree struct_ops ->init() callbacks such as bm_bpf_init() in
fs/binfmt_misc_bpf.c and bpf_io_init() in io_uring/bpf-ops.c call
register_btf_kfunc_id_set() and return its error code.

When btf_apply_deferred() replays BTF_DEFER_ST_OPS while
btf_vmlinux_building() is true, register_btf_kfunc_id_set() runs directly
against btf_vmlinux_new via btf_populate_kfunc_set(). If memory allocation
fails with -ENOMEM in btf_populate_kfunc_set(), it jumps to label end and
frees the entire kfunc_set_tab on btf_vmlinux_new:

btf_populate_kfunc_set()
	...
end:
	btf_free_kfunc_set_tab(btf);
	return ret;

However, in kernel/bpf/bpf_struct_ops.c, bpf_struct_ops_desc_init()
discards the -ENOMEM return value from st_ops->init(btf) and replaces it
with -EINVAL:

	if (st_ops->init(btf)) {
		pr_warn("Error in init bpf_struct_ops %s\n",
			st_ops->name);
		err = -EINVAL;
		goto errout;
	}

As a result, __register_bpf_struct_ops() returns -EINVAL instead of
-ENOMEM to btf_apply_deferred(). Because btf_apply_deferred() only aborts
the round when err == -ENOMEM, wouldn't it log a warning, free all queued
entries in btf_deferred_regs, and return 0, causing bpf_get_btf_vmlinux()
to permanently publish a btf_vmlinux whose kfunc_set_tab was wiped out by
btf_free_kfunc_set_tab()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261031130959.23472-1-wanjay@amazon.com?part=10

  reply	other threads:[~2026-10-31 13:38 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 [this message]
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
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-191982@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