public inbox for bpf@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 0/6] Add support to emit verifier warnings
@ 2026-04-08  2:13 Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 1/6] bpf: Add support for verifier warning messages Kumar Kartikeya Dwivedi
                   ` (5 more replies)
  0 siblings, 6 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:13 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

Currently, there are only two ways of communicating information to the user
when a program is verified, success or failure with a verbose verifier log.
Some information is meant to be more discretionary, e.g. warning about use of
kfuncs that are deprecated, and may be removed in future kernel releases.

An example is shown below.

$ ./test_progs -t kfunc_implicit_args/test_kfunc_implicit_arg_legacy_impl -v
...
2: (b7) r3 = 0                        ; R3=0
3: (85) call bpf_kfunc_implicit_arg_legacy_impl#114683
kfunc_implicit_args.c:40 (insn #3) uses deprecated kfunc bpf_kfunc_implicit_arg_legacy_impl(), which will be removed.
Switch to kfunc bpf_kfunc_implicit_arg_legacy() instead.
For older kernels, choose the correct kfunc using bpf_ksym_exists().
4: R0=scalar()
4: (95) exit
...

With verbose logging, the deprecation warnings are printed inline in the
verifier log. When no log level is chosen, the warning is printed in a
block, as follows:

libbpf: prog 'test_kfunc_implicit_arg_legacy_impl': -- BEGIN PROG LOAD WARNINGS --
kfunc_implicit_args.c:40 (insn #3) uses deprecated kfunc bpf_kfunc_implicit_arg_legacy_impl(), which will be removed.
Switch to kfunc bpf_kfunc_implicit_arg_legacy() instead.
For older kernels, choose the correct kfunc using bpf_ksym_exists().
-- END PROG LOAD WARNINGS --

The set was tested with and without kernel changes applied, so that we
could conclude that libbpf feature detection works correctly and
backwards compatibility is not violated on older kernels. Still, it
would be helpful if other reviewers could recheck all assumptions and
make sure I didn't miss any corner case.

One case I need more opinions on: log_buf.c passes log_buf with
log_level=0 and expects it to be empty, with newer libbpf that won't be
true on older kernels anymore. I don't think this is something that
needs to be addressed, but explicitly noting it here since I saw that
test fail when testing new libbpf on older kernels. The kernel will now
populate such log_buf will "processed N insns ..." log since on failure
libbpf will retry with log_level=1, so unless applications are relying
on log_buf to be empty for correctness it should be ok.

Changelog:
----------
v1 -> v2
v1: https://lore.kernel.org/bpf/20260329212534.3270005-1-memxor@gmail.com

 * Use BTF tags and kfunc flags for deprecation warnings.
 * Reuse verifier log to deliver log messages. (Alexei)
 * Make the mechanism declarative without kfunc lists in verifier.c.

Kumar Kartikeya Dwivedi (6):
  bpf: Add support for verifier warning messages
  bpf: Extract bpf_get_linfo_file_line
  bpf: Make find_linfo widely available
  bpf: Use KF_DEPRECATED to emit verifier warnings
  bpf: Add __bpf_kfunc_replacement() annotation
  libbpf: Flush verifier warning messages by default

 include/linux/bpf.h                           |  3 +
 include/linux/bpf_verifier.h                  |  1 +
 include/linux/btf.h                           |  6 ++
 include/linux/compiler_types.h                | 14 +++-
 kernel/bpf/core.c                             | 66 ++++++++++++++++---
 kernel/bpf/helpers.c                          | 24 ++++---
 kernel/bpf/log.c                              | 49 +-------------
 kernel/bpf/verifier.c                         | 60 ++++++++++++++++-
 tools/lib/bpf/bpf.c                           |  5 +-
 tools/lib/bpf/features.c                      | 56 ++++++++++++++++
 tools/lib/bpf/libbpf.c                        | 20 ++++--
 tools/lib/bpf/libbpf_internal.h               |  2 +
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  3 +-
 13 files changed, 233 insertions(+), 76 deletions(-)


base-commit: 5c662b1c1789f51f79ee9c648681abc8410dfa81
-- 
2.52.0


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH bpf-next v2 1/6] bpf: Add support for verifier warning messages
  2026-04-08  2:13 [PATCH bpf-next v2 0/6] Add support to emit verifier warnings Kumar Kartikeya Dwivedi
@ 2026-04-08  2:13 ` Kumar Kartikeya Dwivedi
  2026-04-08  8:46   ` sun jian
  2026-04-08  2:13 ` [PATCH bpf-next v2 2/6] bpf: Extract bpf_get_linfo_file_line Kumar Kartikeya Dwivedi
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:13 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

Relax conditions such that we allow log_level = 0 to have an associated
log buffer. Add a warn() macro that emits messages to log buffer without
any restrictions, aggregate the warnings emitted, and then use it to
decide whether we reset the log or not.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/bpf_verifier.h |  1 +
 kernel/bpf/log.c             |  6 ++----
 kernel/bpf/verifier.c        | 17 +++++++++++++++--
 3 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 36bfd96d4563..c7b34236a4b5 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -795,6 +795,7 @@ struct bpf_verifier_env {
 	bool bypass_spec_v4;
 	bool seen_direct_write;
 	bool seen_exception;
+	bool warnings;
 	struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
 	const struct bpf_line_info *prev_linfo;
 	struct bpf_verifier_log log;
diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index 37d72b052192..c67fbbbd5768 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -18,13 +18,11 @@ static bool bpf_verifier_log_attr_valid(const struct bpf_verifier_log *log)
 	/* ubuf and len_total should both be specified (or not) together */
 	if (!!log->ubuf != !!log->len_total)
 		return false;
-	/* log buf without log_level is meaningless */
-	if (log->ubuf && log->level == 0)
-		return false;
 	if (log->level & ~BPF_LOG_MASK)
 		return false;
 	if (log->len_total > UINT_MAX >> 2)
 		return false;
+	/* log->ubuf may be set for log->level = 0 to get warning messages. */
 	return true;
 }
 
@@ -229,7 +227,7 @@ int bpf_vlog_finalize(struct bpf_verifier_log *log, u32 *log_size_actual)
 	int err;
 
 	*log_size_actual = 0;
-	if (!log || log->level == 0 || log->level == BPF_LOG_KERNEL)
+	if (!log || (!log->ubuf && log->level == 0) || log->level == BPF_LOG_KERNEL)
 		return 0;
 
 	if (!log->ubuf)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 594260c1f382..86d77d5f7f83 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -47,6 +47,7 @@ static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
 enum bpf_features {
 	BPF_FEAT_RDONLY_CAST_TO_VOID = 0,
 	BPF_FEAT_STREAMS	     = 1,
+	BPF_FEAT_VERIFIER_WARNINGS   = 2,
 	__MAX_BPF_FEAT,
 };
 
@@ -368,6 +369,17 @@ __printf(2, 3) static void verbose(void *private_data, const char *fmt, ...)
 	va_end(args);
 }
 
+__printf(2, 3) static void warn(void *private_data, const char *fmt, ...)
+{
+	struct bpf_verifier_env *env = private_data;
+	va_list args;
+
+	va_start(args, fmt);
+	bpf_verifier_vlog(&env->log, fmt, args);
+	va_end(args);
+	env->warnings = true;
+}
+
 static void verbose_invalid_scalar(struct bpf_verifier_env *env,
 				   struct bpf_reg_state *reg,
 				   struct bpf_retval_range range, const char *ctx,
@@ -2128,7 +2140,8 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
 		if (err)
 			return err;
 	}
-	if (pop_log)
+	/* Preserve warning-only output across branch explorations. */
+	if (pop_log && !(env->warnings && env->log.level == 0))
 		bpf_vlog_reset(&env->log, head->log_pos);
 	if (insn_idx)
 		*insn_idx = head->insn_idx;
@@ -25211,7 +25224,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 
 	ret = do_check(env);
 out:
-	if (!ret && pop_log)
+	if (!ret && pop_log && !env->warnings)
 		bpf_vlog_reset(&env->log, 0);
 	free_states(env);
 	return ret;
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH bpf-next v2 2/6] bpf: Extract bpf_get_linfo_file_line
  2026-04-08  2:13 [PATCH bpf-next v2 0/6] Add support to emit verifier warnings Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 1/6] bpf: Add support for verifier warning messages Kumar Kartikeya Dwivedi
@ 2026-04-08  2:13 ` Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 3/6] bpf: Make find_linfo widely available Kumar Kartikeya Dwivedi
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:13 UTC (permalink / raw)
  To: bpf
  Cc: Puranjay Mohan, Mykyta Yatsenko, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Martin KaFai Lau,
	Eduard Zingerman, Ihor Solodrai, kkd, kernel-team

Extract bpf_get_linfo_file_line as its own function so that the logic to
obtain the file, line, and line number for a given program can be shared
in subsequent patches.

Reviewed-by: Puranjay Mohan <puranjay@kernel.org>
Acked-by: Mykyta Yatsenko <yatsenko@meta.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/bpf.h |  2 ++
 kernel/bpf/core.c   | 29 +++++++++++++++++++++--------
 2 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 30d35d5fe40b..d8fb9d61f5ce 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3945,6 +3945,8 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)
 	return prog->aux->func_idx != 0;
 }
 
+void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
+			     const char **filep, const char **linep, int *nump);
 int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
 			   const char **linep, int *nump);
 struct bpf_prog *bpf_prog_find_from_stack(void);
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 89b89f55415c..ada76f997177 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3315,6 +3315,26 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);
 
 #ifdef CONFIG_BPF_SYSCALL
 
+void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
+			     const char **filep, const char **linep, int *nump)
+{
+	/* Get base component of the file path. */
+	if (filep) {
+		*filep = btf_name_by_offset(btf, linfo->file_name_off);
+		*filep = kbasename(*filep);
+	}
+
+	/* Obtain the source line, and strip whitespace in prefix. */
+	if (linep) {
+		*linep = btf_name_by_offset(btf, linfo->line_off);
+		while (isspace(**linep))
+			*linep += 1;
+	}
+
+	if (nump)
+		*nump = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
+}
+
 int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
 			   const char **linep, int *nump)
 {
@@ -3349,14 +3369,7 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *
 	if (idx == -1)
 		return -ENOENT;
 
-	/* Get base component of the file path. */
-	*filep = btf_name_by_offset(btf, linfo[idx].file_name_off);
-	*filep = kbasename(*filep);
-	/* Obtain the source line, and strip whitespace in prefix. */
-	*linep = btf_name_by_offset(btf, linfo[idx].line_off);
-	while (isspace(**linep))
-		*linep += 1;
-	*nump = BPF_LINE_INFO_LINE_NUM(linfo[idx].line_col);
+	bpf_get_linfo_file_line(btf, &linfo[idx], filep, linep, nump);
 	return 0;
 }
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH bpf-next v2 3/6] bpf: Make find_linfo widely available
  2026-04-08  2:13 [PATCH bpf-next v2 0/6] Add support to emit verifier warnings Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 1/6] bpf: Add support for verifier warning messages Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 2/6] bpf: Extract bpf_get_linfo_file_line Kumar Kartikeya Dwivedi
@ 2026-04-08  2:13 ` Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 4/6] bpf: Use KF_DEPRECATED to emit verifier warnings Kumar Kartikeya Dwivedi
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:13 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

Move find_linfo() as bpf_find_linfo() into core.c to allow for its use
in the verifier in subsequent patches.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/bpf.h |  1 +
 kernel/bpf/core.c   | 37 +++++++++++++++++++++++++++++++++++++
 kernel/bpf/log.c    | 43 +------------------------------------------
 3 files changed, 39 insertions(+), 42 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index d8fb9d61f5ce..0136a108d083 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3945,6 +3945,7 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)
 	return prog->aux->func_idx != 0;
 }
 
+const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off);
 void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
 			     const char **filep, const char **linep, int *nump);
 int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index ada76f997177..066b86e7233c 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3335,6 +3335,43 @@ void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
 		*nump = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
 }
 
+const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off)
+{
+	const struct bpf_line_info *linfo;
+	u32 nr_linfo;
+	int l, r, m;
+
+	nr_linfo = prog->aux->nr_linfo;
+	if (!nr_linfo || insn_off >= prog->len)
+		return NULL;
+
+	linfo = prog->aux->linfo;
+	/* Loop invariant: linfo[l].insn_off <= insns_off.
+	 * linfo[0].insn_off == 0 which always satisfies above condition.
+	 * Binary search is searching for rightmost linfo entry that satisfies
+	 * the above invariant, giving us the desired record that covers given
+	 * instruction offset.
+	 */
+	l = 0;
+	r = nr_linfo - 1;
+	while (l < r) {
+		/* (r - l + 1) / 2 means we break a tie to the right, so if:
+		 * l=1, r=2, linfo[l].insn_off <= insn_off, linfo[r].insn_off > insn_off,
+		 * then m=2, we see that linfo[m].insn_off > insn_off, and so
+		 * r becomes 1 and we exit the loop with correct l==1.
+		 * If the tie was broken to the left, m=1 would end us up in
+		 * an endless loop where l and m stay at 1 and r stays at 2.
+		 */
+		m = l + (r - l + 1) / 2;
+		if (linfo[m].insn_off <= insn_off)
+			l = m;
+		else
+			r = m - 1;
+	}
+
+	return &linfo[l];
+}
+
 int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
 			   const char **linep, int *nump)
 {
diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index c67fbbbd5768..48931d4e5a68 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -327,47 +327,6 @@ __printf(2, 3) void bpf_log(struct bpf_verifier_log *log,
 }
 EXPORT_SYMBOL_GPL(bpf_log);
 
-static const struct bpf_line_info *
-find_linfo(const struct bpf_verifier_env *env, u32 insn_off)
-{
-	const struct bpf_line_info *linfo;
-	const struct bpf_prog *prog;
-	u32 nr_linfo;
-	int l, r, m;
-
-	prog = env->prog;
-	nr_linfo = prog->aux->nr_linfo;
-
-	if (!nr_linfo || insn_off >= prog->len)
-		return NULL;
-
-	linfo = prog->aux->linfo;
-	/* Loop invariant: linfo[l].insn_off <= insns_off.
-	 * linfo[0].insn_off == 0 which always satisfies above condition.
-	 * Binary search is searching for rightmost linfo entry that satisfies
-	 * the above invariant, giving us the desired record that covers given
-	 * instruction offset.
-	 */
-	l = 0;
-	r = nr_linfo - 1;
-	while (l < r) {
-		/* (r - l + 1) / 2 means we break a tie to the right, so if:
-		 * l=1, r=2, linfo[l].insn_off <= insn_off, linfo[r].insn_off > insn_off,
-		 * then m=2, we see that linfo[m].insn_off > insn_off, and so
-		 * r becomes 1 and we exit the loop with correct l==1.
-		 * If the tie was broken to the left, m=1 would end us up in
-		 * an endless loop where l and m stay at 1 and r stays at 2.
-		 */
-		m = l + (r - l + 1) / 2;
-		if (linfo[m].insn_off <= insn_off)
-			l = m;
-		else
-			r = m - 1;
-	}
-
-	return &linfo[l];
-}
-
 static const char *ltrim(const char *s)
 {
 	while (isspace(*s))
@@ -388,7 +347,7 @@ __printf(3, 4) void verbose_linfo(struct bpf_verifier_env *env,
 		return;
 
 	prev_linfo = env->prev_linfo;
-	linfo = find_linfo(env, insn_off);
+	linfo = bpf_find_linfo(env->prog, insn_off);
 	if (!linfo || linfo == prev_linfo)
 		return;
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH bpf-next v2 4/6] bpf: Use KF_DEPRECATED to emit verifier warnings
  2026-04-08  2:13 [PATCH bpf-next v2 0/6] Add support to emit verifier warnings Kumar Kartikeya Dwivedi
                   ` (2 preceding siblings ...)
  2026-04-08  2:13 ` [PATCH bpf-next v2 3/6] bpf: Make find_linfo widely available Kumar Kartikeya Dwivedi
@ 2026-04-08  2:13 ` Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 5/6] bpf: Add __bpf_kfunc_replacement() annotation Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 6/6] libbpf: Flush verifier warning messages by default Kumar Kartikeya Dwivedi
  5 siblings, 0 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:13 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

As documented in kfuncs.rst, introduce a KF_DEPRECATED annotation for
kfuncs and apply it to all existing impl-suffixed kfuncs that are
preserved for backwards compatibility for a few kernel releases, after
we introduced versions with KF_IMPLICIT_ARGS.

Use the kfunc flag to decide whether to emit warnings to the verifier
log or not. Be helpful and point out the file:line in addition to the
instruction when the information is available while emitting the
deprecation warning.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/btf.h                           |  1 +
 kernel/bpf/helpers.c                          | 16 ++++-----
 kernel/bpf/verifier.c                         | 34 +++++++++++++++++++
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  2 +-
 4 files changed, 44 insertions(+), 9 deletions(-)

diff --git a/include/linux/btf.h b/include/linux/btf.h
index 48108471c5b1..86006a2d8b2a 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -79,6 +79,7 @@
 #define KF_ARENA_ARG1   (1 << 14) /* kfunc takes an arena pointer as its first argument */
 #define KF_ARENA_ARG2   (1 << 15) /* kfunc takes an arena pointer as its second argument */
 #define KF_IMPLICIT_ARGS (1 << 16) /* kfunc has implicit arguments supplied by the verifier */
+#define KF_DEPRECATED   (1 << 17) /* kfunc is deprecated and should warn when used */
 
 /*
  * Tag marking a kernel function as a kfunc. This is meant to minimize the
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index bb95e287b0dc..190c0b382f11 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4685,19 +4685,19 @@ BTF_KFUNCS_START(generic_btf_ids)
 BTF_ID_FLAGS(func, crash_kexec, KF_DESTRUCTIVE)
 #endif
 BTF_ID_FLAGS(func, bpf_obj_new, KF_ACQUIRE | KF_RET_NULL | KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_obj_new_impl, KF_ACQUIRE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_obj_new_impl, KF_ACQUIRE | KF_RET_NULL | KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_percpu_obj_new, KF_ACQUIRE | KF_RET_NULL | KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_percpu_obj_new_impl, KF_ACQUIRE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_percpu_obj_new_impl, KF_ACQUIRE | KF_RET_NULL | KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_obj_drop, KF_RELEASE | KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_obj_drop_impl, KF_RELEASE)
+BTF_ID_FLAGS(func, bpf_obj_drop_impl, KF_RELEASE | KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_percpu_obj_drop, KF_RELEASE | KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_percpu_obj_drop_impl, KF_RELEASE)
+BTF_ID_FLAGS(func, bpf_percpu_obj_drop_impl, KF_RELEASE | KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_refcount_acquire, KF_ACQUIRE | KF_RET_NULL | KF_RCU | KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_refcount_acquire_impl, KF_ACQUIRE | KF_RET_NULL | KF_RCU)
+BTF_ID_FLAGS(func, bpf_refcount_acquire_impl, KF_ACQUIRE | KF_RET_NULL | KF_RCU | KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_list_push_front, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_list_push_front_impl)
+BTF_ID_FLAGS(func, bpf_list_push_front_impl, KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_list_push_back, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_list_push_back_impl)
+BTF_ID_FLAGS(func, bpf_list_push_back_impl, KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_list_pop_front, KF_ACQUIRE | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_list_pop_back, KF_ACQUIRE | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_list_front, KF_RET_NULL)
@@ -4706,7 +4706,7 @@ BTF_ID_FLAGS(func, bpf_task_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_task_release, KF_RELEASE)
 BTF_ID_FLAGS(func, bpf_rbtree_remove, KF_ACQUIRE | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_rbtree_add, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_rbtree_add_impl)
+BTF_ID_FLAGS(func, bpf_rbtree_add_impl, KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_rbtree_first, KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_rbtree_root, KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_rbtree_left, KF_RET_NULL)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 86d77d5f7f83..267ab3c36f6f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3229,6 +3229,7 @@ struct bpf_kfunc_desc {
 	u32 func_id;
 	s32 imm;
 	u16 offset;
+	bool warned_deprecated;
 	unsigned long addr;
 };
 
@@ -12426,6 +12427,11 @@ static bool is_kfunc_destructive(struct bpf_kfunc_call_arg_meta *meta)
 	return meta->kfunc_flags & KF_DESTRUCTIVE;
 }
 
+static bool is_kfunc_deprecated(struct bpf_kfunc_call_arg_meta *meta)
+{
+	return meta->kfunc_flags & KF_DEPRECATED;
+}
+
 static bool is_kfunc_rcu(struct bpf_kfunc_call_arg_meta *meta)
 {
 	return meta->kfunc_flags & KF_RCU;
@@ -14583,6 +14589,32 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_ca
 
 static int check_return_code(struct bpf_verifier_env *env, int regno, const char *reg_name);
 
+static void warn_for_deprecated_kfuncs(struct bpf_verifier_env *env,
+				       struct bpf_kfunc_call_arg_meta *meta,
+				       int insn_idx, s16 offset)
+{
+	const struct bpf_line_info *linfo;
+	struct bpf_kfunc_desc *desc;
+	const char *file;
+	int line_num;
+
+	desc = find_kfunc_desc(env->prog, meta->func_id, offset);
+	if (!desc || desc->warned_deprecated || !is_kfunc_deprecated(meta) || !env->prog->aux->btf)
+		return;
+
+	linfo = bpf_find_linfo(env->prog, insn_idx);
+	if (linfo) {
+		bpf_get_linfo_file_line(env->prog->aux->btf, linfo, &file, NULL, &line_num);
+		warn(env, "%s:%d (insn #%d) uses deprecated kfunc %s(), which will be removed.\n",
+		     file, line_num, insn_idx, meta->func_name);
+	} else {
+		warn(env, "(insn #%d) uses deprecated kfunc %s(), which will be removed.\n",
+		     insn_idx, meta->func_name);
+	}
+
+	desc->warned_deprecated = true;
+}
+
 static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 			    int *insn_idx_p)
 {
@@ -14612,6 +14644,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 
 	insn_aux->is_iter_next = is_iter_next_kfunc(&meta);
 
+	warn_for_deprecated_kfuncs(env, &meta, insn_idx, insn->off);
+
 	if (!insn->off &&
 	    (insn->imm == special_kfunc_list[KF_bpf_res_spin_lock] ||
 	     insn->imm == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])) {
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index d876314a4d67..6ad7b2cdfd05 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1326,7 +1326,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1)
 BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_assoc, KF_IMPLICIT_ARGS)
 BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg, KF_IMPLICIT_ARGS)
 BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy, KF_IMPLICIT_ARGS)
-BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy_impl)
+BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy_impl, KF_DEPRECATED)
 BTF_ID_FLAGS(func, bpf_kfunc_trigger_ctx_check)
 BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids)
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH bpf-next v2 5/6] bpf: Add __bpf_kfunc_replacement() annotation
  2026-04-08  2:13 [PATCH bpf-next v2 0/6] Add support to emit verifier warnings Kumar Kartikeya Dwivedi
                   ` (3 preceding siblings ...)
  2026-04-08  2:13 ` [PATCH bpf-next v2 4/6] bpf: Use KF_DEPRECATED to emit verifier warnings Kumar Kartikeya Dwivedi
@ 2026-04-08  2:13 ` Kumar Kartikeya Dwivedi
  2026-04-08  2:13 ` [PATCH bpf-next v2 6/6] libbpf: Flush verifier warning messages by default Kumar Kartikeya Dwivedi
  5 siblings, 0 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:13 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

Add an annotation to emit more contextual messages per deprecated kfunc
about which replacement kfunc can be used in its place.  The macro
itself takes the replacement kfunc as the argument.

This uses BTF declaration tags, and is only supplied as a warning to the
user when the kernel is compiled using LLVM, as GCC lacks equivalent
support. That said, in all cases KF_DEPRECATED does produce a warning,
so this strictly leads to an improved message where possible.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/btf.h                                |  5 +++++
 include/linux/compiler_types.h                     | 14 ++++++++++++--
 kernel/bpf/helpers.c                               |  8 ++++++++
 kernel/bpf/verifier.c                              |  9 +++++++++
 .../testing/selftests/bpf/test_kmods/bpf_testmod.c |  1 +
 5 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/include/linux/btf.h b/include/linux/btf.h
index 86006a2d8b2a..50cc8aa50e7a 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -89,6 +89,11 @@
  */
 #define __bpf_kfunc __used __retain __noclone noinline
 
+#define BPF_KFUNC_DECL_TAG_REPLACEMENT "bpf:kfunc:replacement:"
+
+#define __bpf_kfunc_replacement(replacement) \
+	BTF_DECL_TAG(BPF_KFUNC_DECL_TAG_REPLACEMENT __stringify(replacement))
+
 #define __bpf_kfunc_start_defs()					       \
 	__diag_push();							       \
 	__diag_ignore_all("-Wmissing-declarations",			       \
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index 890076d0974b..ef840212dc56 100644
--- a/include/linux/compiler_types.h
+++ b/include/linux/compiler_types.h
@@ -35,10 +35,20 @@
  * see https://github.com/rust-lang/rust-bindgen/issues/2244.
  */
 #if defined(CONFIG_DEBUG_INFO_BTF) && defined(CONFIG_PAHOLE_HAS_BTF_TAG) && \
-	__has_attribute(btf_type_tag) && !defined(__BINDGEN__)
-# define BTF_TYPE_TAG(value) __attribute__((btf_type_tag(#value)))
+	!defined(__BINDGEN__)
+# if __has_attribute(btf_type_tag)
+#  define BTF_TYPE_TAG(value) __attribute__((btf_type_tag(#value)))
+# else
+#  define BTF_TYPE_TAG(value) /* nothing */
+# endif
+# if __has_attribute(btf_decl_tag)
+#  define BTF_DECL_TAG(value) __attribute__((btf_decl_tag(value)))
+# else
+#  define BTF_DECL_TAG(value) /* nothing */
+# endif
 #else
 # define BTF_TYPE_TAG(value) /* nothing */
+# define BTF_DECL_TAG(value) /* nothing */
 #endif
 
 #include <linux/compiler-context-analysis.h>
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 190c0b382f11..a07ebc0597a3 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -2328,6 +2328,7 @@ __bpf_kfunc void *bpf_obj_new(u64 local_type_id__k, struct btf_struct_meta *meta
 	return p;
 }
 
+__bpf_kfunc_replacement(bpf_obj_new)
 __bpf_kfunc void *bpf_obj_new_impl(u64 local_type_id__k, void *meta__ign)
 {
 	return bpf_obj_new(local_type_id__k, meta__ign);
@@ -2352,6 +2353,7 @@ __bpf_kfunc void *bpf_percpu_obj_new(u64 local_type_id__k, struct btf_struct_met
 	return bpf_mem_alloc(&bpf_global_percpu_ma, size);
 }
 
+__bpf_kfunc_replacement(bpf_percpu_obj_new)
 __bpf_kfunc void *bpf_percpu_obj_new_impl(u64 local_type_id__k, void *meta__ign)
 {
 	return bpf_percpu_obj_new(local_type_id__k, meta__ign);
@@ -2395,6 +2397,7 @@ __bpf_kfunc void bpf_obj_drop(void *p__alloc, struct btf_struct_meta *meta)
 	__bpf_obj_drop_impl(p, meta ? meta->record : NULL, false);
 }
 
+__bpf_kfunc_replacement(bpf_obj_drop)
 __bpf_kfunc void bpf_obj_drop_impl(void *p__alloc, void *meta__ign)
 {
 	return bpf_obj_drop(p__alloc, meta__ign);
@@ -2413,6 +2416,7 @@ __bpf_kfunc void bpf_percpu_obj_drop(void *p__alloc, struct btf_struct_meta *met
 	bpf_mem_free_rcu(&bpf_global_percpu_ma, p__alloc);
 }
 
+__bpf_kfunc_replacement(bpf_percpu_obj_drop)
 __bpf_kfunc void bpf_percpu_obj_drop_impl(void *p__alloc, void *meta__ign)
 {
 	bpf_percpu_obj_drop(p__alloc, meta__ign);
@@ -2445,6 +2449,7 @@ __bpf_kfunc void *bpf_refcount_acquire(void *p__refcounted_kptr, struct btf_stru
 	return (void *)p__refcounted_kptr;
 }
 
+__bpf_kfunc_replacement(bpf_refcount_acquire)
 __bpf_kfunc void *bpf_refcount_acquire_impl(void *p__refcounted_kptr, void *meta__ign)
 {
 	return bpf_refcount_acquire(p__refcounted_kptr, meta__ign);
@@ -2499,6 +2504,7 @@ __bpf_kfunc int bpf_list_push_front(struct bpf_list_head *head,
 	return __bpf_list_add(n, head, false, meta ? meta->record : NULL, off);
 }
 
+__bpf_kfunc_replacement(bpf_list_push_front)
 __bpf_kfunc int bpf_list_push_front_impl(struct bpf_list_head *head,
 					 struct bpf_list_node *node,
 					 void *meta__ign, u64 off)
@@ -2528,6 +2534,7 @@ __bpf_kfunc int bpf_list_push_back(struct bpf_list_head *head,
 	return __bpf_list_add(n, head, true, meta ? meta->record : NULL, off);
 }
 
+__bpf_kfunc_replacement(bpf_list_push_back)
 __bpf_kfunc int bpf_list_push_back_impl(struct bpf_list_head *head,
 					struct bpf_list_node *node,
 					void *meta__ign, u64 off)
@@ -2668,6 +2675,7 @@ __bpf_kfunc int bpf_rbtree_add(struct bpf_rb_root *root,
 	return __bpf_rbtree_add(root, n, (void *)less, meta ? meta->record : NULL, off);
 }
 
+__bpf_kfunc_replacement(bpf_rbtree_add)
 __bpf_kfunc int bpf_rbtree_add_impl(struct bpf_rb_root *root, struct bpf_rb_node *node,
 				    bool (less)(struct bpf_rb_node *a, const struct bpf_rb_node *b),
 				    void *meta__ign, u64 off)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 267ab3c36f6f..535b4af710a8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -14594,6 +14594,8 @@ static void warn_for_deprecated_kfuncs(struct bpf_verifier_env *env,
 				       int insn_idx, s16 offset)
 {
 	const struct bpf_line_info *linfo;
+	const char *replacement;
+	const struct btf_type *t;
 	struct bpf_kfunc_desc *desc;
 	const char *file;
 	int line_num;
@@ -14612,6 +14614,13 @@ static void warn_for_deprecated_kfuncs(struct bpf_verifier_env *env,
 		     insn_idx, meta->func_name);
 	}
 
+	t = btf_type_by_id(meta->btf, meta->func_id);
+	replacement = btf_find_decl_tag_value(meta->btf, t, -1, BPF_KFUNC_DECL_TAG_REPLACEMENT);
+	if (!IS_ERR(replacement) && !str_is_empty(replacement)) {
+		warn(env, "Switch to kfunc %s() instead.\n", replacement);
+		warn(env, "For older kernels, choose the correct kfunc using bpf_ksym_exists().\n");
+	}
+
 	desc->warned_deprecated = true;
 }
 
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 6ad7b2cdfd05..7391247e3d18 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1252,6 +1252,7 @@ __bpf_kfunc int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, st
 
 __bpf_kfunc int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux);
 __bpf_kfunc int bpf_kfunc_implicit_arg_legacy(int a, int b, struct bpf_prog_aux *aux);
+__bpf_kfunc_replacement(bpf_kfunc_implicit_arg_legacy)
 __bpf_kfunc int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux);
 
 /* hook targets */
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH bpf-next v2 6/6] libbpf: Flush verifier warning messages by default
  2026-04-08  2:13 [PATCH bpf-next v2 0/6] Add support to emit verifier warnings Kumar Kartikeya Dwivedi
                   ` (4 preceding siblings ...)
  2026-04-08  2:13 ` [PATCH bpf-next v2 5/6] bpf: Add __bpf_kfunc_replacement() annotation Kumar Kartikeya Dwivedi
@ 2026-04-08  2:13 ` Kumar Kartikeya Dwivedi
  2026-04-08  2:21   ` Kumar Kartikeya Dwivedi
  5 siblings, 1 reply; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:13 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

Allow passing a log buffer with log_level = 0, and also set the log
buffer by default when loading a program, so that warnings can be
captured and flushed, such that we can notify users of deprecation
messages from the verifier even if program succeeds in loading.

Use the presence of enum bpf_features member to disable passing log_buf
with log_level = 0 as the verifier will reject the program due to an
invalid attribute. Low-level bpf_prog_load() API is relaxed, and will
continue passing on older kernels as long as callers don't pass log_buf
unconditionally.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 tools/lib/bpf/bpf.c             |  5 +--
 tools/lib/bpf/features.c        | 56 +++++++++++++++++++++++++++++++++
 tools/lib/bpf/libbpf.c          | 20 +++++++-----
 tools/lib/bpf/libbpf_internal.h |  2 ++
 4 files changed, 74 insertions(+), 9 deletions(-)

diff --git a/tools/lib/bpf/bpf.c b/tools/lib/bpf/bpf.c
index 5846de364209..01dd8a126ef6 100644
--- a/tools/lib/bpf/bpf.c
+++ b/tools/lib/bpf/bpf.c
@@ -315,11 +315,12 @@ int bpf_prog_load(enum bpf_prog_type prog_type,
 	attr.fd_array = ptr_to_u64(OPTS_GET(opts, fd_array, NULL));
 	attr.fd_array_cnt = OPTS_GET(opts, fd_array_cnt, 0);
 
-	if (log_level) {
+	/* Pass log_buf/log_size independently so log_level=0 loads can collect warnings. */
+	if (log_buf) {
 		attr.log_buf = ptr_to_u64(log_buf);
 		attr.log_size = log_size;
-		attr.log_level = log_level;
 	}
+	attr.log_level = log_level;
 
 	fd = sys_bpf_prog_load(&attr, attr_sz, attempts);
 	OPTS_SET(opts, log_true_size, attr.log_true_size);
diff --git a/tools/lib/bpf/features.c b/tools/lib/bpf/features.c
index 4f19a0d79b0c..a9091c66ed32 100644
--- a/tools/lib/bpf/features.c
+++ b/tools/lib/bpf/features.c
@@ -208,6 +208,59 @@ static int probe_kern_btf_type_tag(int token_fd)
 					     strs, sizeof(strs), token_fd));
 }
 
+static bool btf_type_has_enum_value(const struct btf *btf, const struct btf_type *t,
+				    const char *value_name)
+{
+	int i, vlen = btf_vlen(t);
+
+	if (btf_is_enum(t)) {
+		const struct btf_enum *e = btf_enum(t);
+
+		for (i = 0; i < vlen; i++, e++) {
+			if (strcmp(btf__name_by_offset(btf, e->name_off), value_name) == 0)
+				return true;
+		}
+	} else if (btf_is_enum64(t)) {
+		const struct btf_enum64 *e = btf_enum64(t);
+
+		for (i = 0; i < vlen; i++, e++) {
+			if (strcmp(btf__name_by_offset(btf, e->name_off), value_name) == 0)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+static int probe_kern_verifier_warnings(int token_fd)
+{
+	const struct btf_type *t;
+	struct btf *btf;
+	bool found = false;
+	__s32 type_id;
+
+	(void)token_fd;
+
+	btf = btf__load_vmlinux_btf();
+	if (libbpf_get_error(btf))
+		return 0;
+
+	type_id = btf__find_by_name_kind(btf, "bpf_features", BTF_KIND_ENUM);
+	if (type_id < 0)
+		type_id = btf__find_by_name_kind(btf, "bpf_features", BTF_KIND_ENUM64);
+	if (type_id < 0) {
+		btf__free(btf);
+		return 0;
+	}
+
+	t = btf__type_by_id(btf, type_id);
+	if (t)
+		found = btf_type_has_enum_value(btf, t, "BPF_FEAT_VERIFIER_WARNINGS");
+
+	btf__free(btf);
+	return found;
+}
+
 static int probe_kern_array_mmap(int token_fd)
 {
 	LIBBPF_OPTS(bpf_map_create_opts, opts,
@@ -669,6 +722,9 @@ static struct kern_feature_desc {
 	[FEAT_BTF_TYPE_TAG] = {
 		"BTF_KIND_TYPE_TAG support", probe_kern_btf_type_tag,
 	},
+	[FEAT_VERIFIER_WARNINGS] = {
+		"success-path verifier warnings", probe_kern_verifier_warnings,
+	},
 	[FEAT_MEMCG_ACCOUNT] = {
 		"memcg-based memory accounting", probe_memcg_account,
 	},
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 42bdba4efd0c..ea8ac67fcb3f 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -7832,10 +7832,13 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
 {
 	LIBBPF_OPTS(bpf_prog_load_opts, load_attr);
 	const char *prog_name = NULL;
+	const size_t warn_log_buf_size = 4096;
 	size_t log_buf_size = 0;
 	char *log_buf = NULL, *tmp;
 	bool own_log_buf = true;
 	__u32 log_level = prog->log_level;
+	bool want_verifier_warnings = log_level == 0 &&
+		kernel_supports(obj, FEAT_VERIFIER_WARNINGS);
 	int ret, err;
 
 	/* Be more helpful by rejecting programs that can't be validated early
@@ -7912,12 +7915,11 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
 	}
 
 retry_load:
-	/* if log_level is zero, we don't request logs initially even if
-	 * custom log_buf is specified; if the program load fails, then we'll
-	 * bump log_level to 1 and use either custom log_buf or we'll allocate
-	 * our own and retry the load to get details on what failed
+	/* If supported, pass a buffer with log_level=0 so the kernel can report
+	 * success-path warnings. If the program load fails, retry with
+	 * log_level=1 to get details on what failed.
 	 */
-	if (log_level) {
+	if (log_level || want_verifier_warnings) {
 		if (prog->log_buf) {
 			log_buf = prog->log_buf;
 			log_buf_size = prog->log_size;
@@ -7927,7 +7929,8 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
 			log_buf_size = obj->log_size;
 			own_log_buf = false;
 		} else {
-			log_buf_size = max((size_t)BPF_LOG_BUF_SIZE, log_buf_size * 2);
+			log_buf_size = max(log_level ? (size_t)BPF_LOG_BUF_SIZE : warn_log_buf_size,
+					   log_buf_size * 2);
 			tmp = realloc(log_buf, log_buf_size);
 			if (!tmp) {
 				ret = -ENOMEM;
@@ -7945,7 +7948,10 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
 
 	ret = bpf_prog_load(prog->type, prog_name, license, insns, insns_cnt, &load_attr);
 	if (ret >= 0) {
-		if (log_level && own_log_buf) {
+		if (want_verifier_warnings && log_level == 0 && load_attr.log_true_size) {
+			pr_warn("prog '%s': -- BEGIN PROG LOAD WARNINGS --\n%s-- END PROG LOAD WARNINGS --\n",
+				prog->name, log_buf);
+		} else if (log_level && own_log_buf) {
 			pr_debug("prog '%s': -- BEGIN PROG LOAD LOG --\n%s-- END PROG LOAD LOG --\n",
 				 prog->name, log_buf);
 		}
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index cabdaef79098..7c72ade7a179 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -378,6 +378,8 @@ enum kern_feature_id {
 	FEAT_BTF_DECL_TAG,
 	/* BTF_KIND_TYPE_TAG support */
 	FEAT_BTF_TYPE_TAG,
+	/* Kernel supports success-path verifier warnings */
+	FEAT_VERIFIER_WARNINGS,
 	/* memcg-based accounting for BPF maps and progs */
 	FEAT_MEMCG_ACCOUNT,
 	/* BPF cookie (bpf_get_attach_cookie() BPF helper) support */
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH bpf-next v2 6/6] libbpf: Flush verifier warning messages by default
  2026-04-08  2:13 ` [PATCH bpf-next v2 6/6] libbpf: Flush verifier warning messages by default Kumar Kartikeya Dwivedi
@ 2026-04-08  2:21   ` Kumar Kartikeya Dwivedi
  0 siblings, 0 replies; 9+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-04-08  2:21 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

On Wed, 8 Apr 2026 at 04:14, Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> Allow passing a log buffer with log_level = 0, and also set the log
> buffer by default when loading a program, so that warnings can be
> captured and flushed, such that we can notify users of deprecation
> messages from the verifier even if program succeeds in loading.
>
> Use the presence of enum bpf_features member to disable passing log_buf
> with log_level = 0 as the verifier will reject the program due to an
> invalid attribute. Low-level bpf_prog_load() API is relaxed, and will
> continue passing on older kernels as long as callers don't pass log_buf
> unconditionally.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
> [...]
>
>  retry_load:
> -       /* if log_level is zero, we don't request logs initially even if
> -        * custom log_buf is specified; if the program load fails, then we'll
> -        * bump log_level to 1 and use either custom log_buf or we'll allocate
> -        * our own and retry the load to get details on what failed
> +       /* If supported, pass a buffer with log_level=0 so the kernel can report
> +        * success-path warnings. If the program load fails, retry with
> +        * log_level=1 to get details on what failed.
>          */
> -       if (log_level) {
> +       if (log_level || want_verifier_warnings) {
>                 if (prog->log_buf) {
>                         log_buf = prog->log_buf;
>                         log_buf_size = prog->log_size;
> @@ -7927,7 +7929,8 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
>                         log_buf_size = obj->log_size;
>                         own_log_buf = false;
>                 } else {
> -                       log_buf_size = max((size_t)BPF_LOG_BUF_SIZE, log_buf_size * 2);
> +                       log_buf_size = max(log_level ? (size_t)BPF_LOG_BUF_SIZE : warn_log_buf_size,
> +                                          log_buf_size * 2);
>                         tmp = realloc(log_buf, log_buf_size);
>                         if (!tmp) {
>                                 ret = -ENOMEM;
> @@ -7945,7 +7948,10 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
>
>         ret = bpf_prog_load(prog->type, prog_name, license, insns, insns_cnt, &load_attr);
>         if (ret >= 0) {
> -               if (log_level && own_log_buf) {
> +               if (want_verifier_warnings && log_level == 0 && load_attr.log_true_size) {

I was wondering whether to use own_log_buf here or not, but would like
a second opinion.

> +                       pr_warn("prog '%s': -- BEGIN PROG LOAD WARNINGS --\n%s-- END PROG LOAD WARNINGS --\n",
> +                               prog->name, log_buf);
> +               } else if (log_level && own_log_buf) {
>                         pr_debug("prog '%s': -- BEGIN PROG LOAD LOG --\n%s-- END PROG LOAD LOG --\n",
>                                  prog->name, log_buf);
>                 }
> diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
> index cabdaef79098..7c72ade7a179 100644
> --- a/tools/lib/bpf/libbpf_internal.h
> +++ b/tools/lib/bpf/libbpf_internal.h
> @@ -378,6 +378,8 @@ enum kern_feature_id {
>         FEAT_BTF_DECL_TAG,
>         /* BTF_KIND_TYPE_TAG support */
>         FEAT_BTF_TYPE_TAG,
> +       /* Kernel supports success-path verifier warnings */
> +       FEAT_VERIFIER_WARNINGS,
>         /* memcg-based accounting for BPF maps and progs */
>         FEAT_MEMCG_ACCOUNT,
>         /* BPF cookie (bpf_get_attach_cookie() BPF helper) support */
> --
> 2.52.0
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH bpf-next v2 1/6] bpf: Add support for verifier warning messages
  2026-04-08  2:13 ` [PATCH bpf-next v2 1/6] bpf: Add support for verifier warning messages Kumar Kartikeya Dwivedi
@ 2026-04-08  8:46   ` sun jian
  0 siblings, 0 replies; 9+ messages in thread
From: sun jian @ 2026-04-08  8:46 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi
  Cc: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Ihor Solodrai, kkd,
	kernel-team

On Wed, Apr 8, 2026 at 10:14 AM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> Relax conditions such that we allow log_level = 0 to have an associated
> log buffer. Add a warn() macro that emits messages to log buffer without
> any restrictions, aggregate the warnings emitted, and then use it to
> decide whether we reset the log or not.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
>  include/linux/bpf_verifier.h |  1 +
>  kernel/bpf/log.c             |  6 ++----
>  kernel/bpf/verifier.c        | 17 +++++++++++++++--
>  3 files changed, 18 insertions(+), 6 deletions(-)
>
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 36bfd96d4563..c7b34236a4b5 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -795,6 +795,7 @@ struct bpf_verifier_env {
>         bool bypass_spec_v4;
>         bool seen_direct_write;
>         bool seen_exception;
> +       bool warnings;
>         struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
>         const struct bpf_line_info *prev_linfo;
>         struct bpf_verifier_log log;
> diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
> index 37d72b052192..c67fbbbd5768 100644
> --- a/kernel/bpf/log.c
> +++ b/kernel/bpf/log.c
> @@ -18,13 +18,11 @@ static bool bpf_verifier_log_attr_valid(const struct bpf_verifier_log *log)
>         /* ubuf and len_total should both be specified (or not) together */
>         if (!!log->ubuf != !!log->len_total)
>                 return false;
> -       /* log buf without log_level is meaningless */
> -       if (log->ubuf && log->level == 0)
> -               return false;
>         if (log->level & ~BPF_LOG_MASK)
>                 return false;
>         if (log->len_total > UINT_MAX >> 2)
>                 return false;
> +       /* log->ubuf may be set for log->level = 0 to get warning messages. */
>         return true;
>  }
>
> @@ -229,7 +227,7 @@ int bpf_vlog_finalize(struct bpf_verifier_log *log, u32 *log_size_actual)
>         int err;
>
>         *log_size_actual = 0;
> -       if (!log || log->level == 0 || log->level == BPF_LOG_KERNEL)
> +       if (!log || (!log->ubuf && log->level == 0) || log->level == BPF_LOG_KERNEL)
>                 return 0;
>
>         if (!log->ubuf)
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 594260c1f382..86d77d5f7f83 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -47,6 +47,7 @@ static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
>  enum bpf_features {
>         BPF_FEAT_RDONLY_CAST_TO_VOID = 0,
>         BPF_FEAT_STREAMS             = 1,
> +       BPF_FEAT_VERIFIER_WARNINGS   = 2,
>         __MAX_BPF_FEAT,
>  };
>
> @@ -368,6 +369,17 @@ __printf(2, 3) static void verbose(void *private_data, const char *fmt, ...)
>         va_end(args);
>  }
>
> +__printf(2, 3) static void warn(void *private_data, const char *fmt, ...)
> +{
> +       struct bpf_verifier_env *env = private_data;
> +       va_list args;
> +
> +       va_start(args, fmt);
> +       bpf_verifier_vlog(&env->log, fmt, args);
> +       va_end(args);
> +       env->warnings = true;
> +}
> +
>  static void verbose_invalid_scalar(struct bpf_verifier_env *env,
>                                    struct bpf_reg_state *reg,
>                                    struct bpf_retval_range range, const char *ctx,
> @@ -2128,7 +2140,8 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
>                 if (err)
>                         return err;
>         }
> -       if (pop_log)
> +       /* Preserve warning-only output across branch explorations. */
> +       if (pop_log && !(env->warnings && env->log.level == 0))
IIUC, this can drop some warnings when log_level > 0, which seems to
be a reasonable
design trade-off to me. Might it be worth mentioning this in the commit message?
>                 bpf_vlog_reset(&env->log, head->log_pos);
>         if (insn_idx)
>                 *insn_idx = head->insn_idx;
> @@ -25211,7 +25224,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
>
>         ret = do_check(env);
>  out:
> -       if (!ret && pop_log)
> +       if (!ret && pop_log && !env->warnings)
>                 bpf_vlog_reset(&env->log, 0);
>         free_states(env);
>         return ret;
> --
> 2.52.0
>
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-04-08  8:46 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-08  2:13 [PATCH bpf-next v2 0/6] Add support to emit verifier warnings Kumar Kartikeya Dwivedi
2026-04-08  2:13 ` [PATCH bpf-next v2 1/6] bpf: Add support for verifier warning messages Kumar Kartikeya Dwivedi
2026-04-08  8:46   ` sun jian
2026-04-08  2:13 ` [PATCH bpf-next v2 2/6] bpf: Extract bpf_get_linfo_file_line Kumar Kartikeya Dwivedi
2026-04-08  2:13 ` [PATCH bpf-next v2 3/6] bpf: Make find_linfo widely available Kumar Kartikeya Dwivedi
2026-04-08  2:13 ` [PATCH bpf-next v2 4/6] bpf: Use KF_DEPRECATED to emit verifier warnings Kumar Kartikeya Dwivedi
2026-04-08  2:13 ` [PATCH bpf-next v2 5/6] bpf: Add __bpf_kfunc_replacement() annotation Kumar Kartikeya Dwivedi
2026-04-08  2:13 ` [PATCH bpf-next v2 6/6] libbpf: Flush verifier warning messages by default Kumar Kartikeya Dwivedi
2026-04-08  2:21   ` Kumar Kartikeya Dwivedi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox