bpf.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null
@ 2026-08-26 18:18 Eduard Zingerman
  2026-08-26 18:18 ` [PATCH bpf-next v2 2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug Eduard Zingerman
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Eduard Zingerman @ 2026-08-26 18:18 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song, npc,
	Eduard Zingerman

Nicholas Carlini reported a bug when verifier can incorrectly infer
that a pointer is non-null. The bug occurs when two pointers are
compared and one of them has a type w/o PTR_MAYBE_NULL flag,
but which allows a value to be NULL at runtime.
Here is an example:

  // `a` is PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED
  // `a` is 0 at runtime.
  // `b` is PTR_TO_MAP_VALUE | PTR_MAYBE_NULL
  void *a = bpf_rdonly_cast(0, 0);
  int  *b = bpf_map_lookup_elem(...);

  if (a == b)
    *b = 42;  // verifier does not catch null pointer dereference

This happens because of a special case in check_cond_jmp_op(),
which attempts to strip PTR_MAYBE_NULL flags from pointer types,
when processing comparisons like `rA == rB`, if either rA or rB can't
be null.

The non-null property is derived based on the absence of
PTR_MAYBE_NULL flag on rA's or rB's type. But that is not sufficient
for types like PTR_TO_MEM, as in the example.

This patch replaces type_may_be_null() call with reg_not_null(),
which contains an allowlist of types for which absence of
PTR_MAYBE_NULL actually means that the value can't be NULL at runtime.

At the moment, the list in the reg_not_null() omits two types for
which PTR_MAYBE_NULL is applicable: PTR_TO_XDP_SOCK and PTR_TO_BUF.
In order to remain backward compatible, and assuming that only
comparison between pointers of the same type makes sense,
this commit extends reg_not_null(). W/o such an extension e.g.
verifier_jeq_infer_not_null/null_ptr_to_map_value fails.

reg_not_null() can be extended further, but I deem that out of scope
for the fix at hand. Explicit base_type(...) != PTR_TO_BTF_ID
checks in the check_cond_jmp_op() can be removed with migration to
reg_not_null(), but that is a behavioural change, as the special case
would start matching for PTR_TO_BTF_ID that is also is_trusted_reg().
I omit the behavioural change from this commit.

Fixes: befae75856ab ("bpf: propagate nullness information for reg to reg comparisons")
Suggested-by: Nicholas Carlini <npc@anthropic.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>

---
  v1->v2:
  - no changes
---
 kernel/bpf/verifier.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e036ae20bf6b..897ec4b38dea 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -355,6 +355,8 @@ static bool reg_not_null(struct bpf_verifier_env *env, const struct bpf_reg_stat
 	type = base_type(type);
 	return type == PTR_TO_SOCKET ||
 		type == PTR_TO_TCP_SOCK ||
+		type == PTR_TO_XDP_SOCK ||
+		type == PTR_TO_BUF ||
 		type == PTR_TO_MAP_VALUE ||
 		type == PTR_TO_MAP_KEY ||
 		type == PTR_TO_SOCK_COMMON ||
@@ -17085,7 +17087,6 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 	 */
 	if (!is_jmp32 && BPF_SRC(insn->code) == BPF_X &&
 	    __is_pointer_value(false, src_reg) && __is_pointer_value(false, dst_reg) &&
-	    type_may_be_null(src_reg->type) != type_may_be_null(dst_reg->type) &&
 	    base_type(src_reg->type) != PTR_TO_BTF_ID &&
 	    base_type(dst_reg->type) != PTR_TO_BTF_ID) {
 		eq_branch_regs = NULL;
@@ -17101,9 +17102,11 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 			break;
 		}
 		if (eq_branch_regs) {
-			if (type_may_be_null(src_reg->type))
+			/* src == dst && dst != NULL => src != NULL */
+			if (reg_not_null(env, dst_reg) && type_may_be_null(src_reg->type))
 				mark_ptr_not_null_reg(&eq_branch_regs[insn->src_reg]);
-			else
+			/* src == dst && src != NULL => dst != NULL */
+			if (reg_not_null(env, src_reg) && type_may_be_null(dst_reg->type))
 				mark_ptr_not_null_reg(&eq_branch_regs[insn->dst_reg]);
 		}
 	}

-- 
2.53.0

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

* [PATCH bpf-next v2 2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug
  2026-08-26 18:18 [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null Eduard Zingerman
@ 2026-08-26 18:18 ` Eduard Zingerman
  2026-08-26 19:25   ` bot+bpf-ci
  2026-08-26 19:25 ` [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null bot+bpf-ci
  2026-08-27  1:50 ` patchwork-bot+netdevbpf
  2 siblings, 1 reply; 5+ messages in thread
From: Eduard Zingerman @ 2026-08-26 18:18 UTC (permalink / raw)
  To: bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song, npc,
	Eduard Zingerman

A comparison between PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED and
PTR_TO_MAP_VALUE_OR_NULL should not infer that map pointer is not null.
A bug in check_cond_jmp_op() made such inference possible.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>

---
  v1->v2:
  - selftest updated to use regular BPF_MAP_TYPE_HASH map
    (bot+bpf-ci).
---
 .../bpf/progs/verifier_jeq_infer_not_null.c        | 52 ++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c
index 3d1e8de4390c..b412a542ef76 100644
--- a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c
+++ b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c
@@ -3,7 +3,9 @@
 
 #include <linux/bpf.h>
 #include <bpf/bpf_helpers.h>
+#include <stdbool.h>
 #include "bpf_misc.h"
+#include "bpf_kfuncs.h"
 
 struct {
 	__uint(type, BPF_MAP_TYPE_XSKMAP);
@@ -12,6 +14,13 @@ struct {
 	__type(value, int);
 } map_xskmap SEC(".maps");
 
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 1);
+	__type(key, int);
+	__type(value, int);
+} map_hash SEC(".maps");
+
 /* This is equivalent to the following program:
  *
  *   r6 = skb->sk;
@@ -264,4 +273,47 @@ __naked void jne_reg_reg_null_check(void)
         : __clobber_all);
 }
 
+/*
+ * A comparison between PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED and
+ * PTR_TO_MAP_VALUE_OR_NULL should not infer that map pointer is not null.
+ * A bug in check_cond_jmp_op() made such inference possible.
+ */
+SEC("raw_tp")
+__failure
+__msg("error: invalid dereference of R0 (a nullable map value pointer)")
+__msg(">>> 11 | (61) r0 = *(u32 *)(r0 +0)")
+__naked void untrusted_mem_does_not_infer_map_value_non_null(void)
+{
+	asm volatile ("					\
+	/* r6 = bpf_rdonly_cast(0, 0); */		\
+	r1 = 0;						\
+	r2 = 0;						\
+	call %[bpf_rdonly_cast];			\
+	r6 = r0;					\
+	/* r0 = bpf_map_lookup_elem(map_hash, &key); */	\
+	*(u64 *)(r10 - 8) = 0;				\
+	r1 = %[map_hash] ll;				\
+	r2 = r10;					\
+	r2 += -8;					\
+	call %[bpf_map_lookup_elem];			\
+	/*						\
+	 * buggy verifier assumed that r6 can't be null	\
+	 * and marked r0 non-null as well.		\
+	 */						\
+	if r6 != r0 goto 1f;				\
+	r0 = *(u32 *)(r0 + 0);				\
+1:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm(bpf_rdonly_cast),
+	  __imm(bpf_map_lookup_elem),
+	  __imm_addr(map_hash)
+	: __clobber_all);
+}
+
+void kfunc_root(void)
+{
+	bpf_rdonly_cast(0, 0);
+}
+
 char _license[] SEC("license") = "GPL";

-- 
2.53.0

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

* Re: [PATCH bpf-next v2 2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug
  2026-08-26 18:18 ` [PATCH bpf-next v2 2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug Eduard Zingerman
@ 2026-08-26 19:25   ` bot+bpf-ci
  0 siblings, 0 replies; 5+ messages in thread
From: bot+bpf-ci @ 2026-08-26 19:25 UTC (permalink / raw)
  To: eddyz87, bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song, npc,
	eddyz87, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
	clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 733 bytes --]

> selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug
>
> A comparison between PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED and
> PTR_TO_MAP_VALUE_OR_NULL should not infer that map pointer is not null.
> A bug in check_cond_jmp_op() made such inference possible.
>
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>

This looks like a test for a major verifier bug. Should this include:

  Fixes: befae75856ab ("bpf: propagate nullness information for reg to reg comparisons")


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33000653934

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

* Re: [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null
  2026-08-26 18:18 [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null Eduard Zingerman
  2026-08-26 18:18 ` [PATCH bpf-next v2 2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug Eduard Zingerman
@ 2026-08-26 19:25 ` bot+bpf-ci
  2026-08-27  1:50 ` patchwork-bot+netdevbpf
  2 siblings, 0 replies; 5+ messages in thread
From: bot+bpf-ci @ 2026-08-26 19:25 UTC (permalink / raw)
  To: eddyz87, bpf, ast
  Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song, npc,
	eddyz87, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
	clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2954 bytes --]

> bpf: check_cond_jmp_op(): properly infer if register is null
>
> Nicholas Carlini reported a bug when verifier can incorrectly infer
> that a pointer is non-null. The bug occurs when two pointers are
> compared and one of them has a type w/o PTR_MAYBE_NULL flag,
> but which allows a value to be NULL at runtime.
> Here is an example:
>
>   // `a` is PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED
>   // `a` is 0 at runtime.
>   // `b` is PTR_TO_MAP_VALUE | PTR_MAYBE_NULL
>   void *a = bpf_rdonly_cast(0, 0);
>   int  *b = bpf_map_lookup_elem(...);
>
>   if (a == b)
>     *b = 42;  // verifier does not catch null pointer dereference
>
> This happens because of a special case in check_cond_jmp_op(),
> which attempts to strip PTR_MAYBE_NULL flags from pointer types,
> when processing comparisons like `rA == rB`, if either rA or rB can't
> be null.
>
> The non-null property is derived based on the absence of
> PTR_MAYBE_NULL flag on rA's or rB's type. But that is not sufficient
> for types like PTR_TO_MEM, as in the example.
>
> This patch replaces type_may_be_null() call with reg_not_null(),
> which contains an allowlist of types for which absence of
> PTR_MAYBE_NULL actually means that the value can't be NULL at runtime.
>
> At the moment, the list in the reg_not_null() omits two types for
> which PTR_MAYBE_NULL is applicable: PTR_TO_XDP_SOCK and PTR_TO_BUF.
> In order to remain backward compatible, and assuming that only
> comparison between pointers of the same type makes sense,
> this commit extends reg_not_null(). W/o such an extension e.g.
> verifier_jeq_infer_not_null/null_ptr_to_map_value fails.
>
> reg_not_null() can be extended further, but I deem that out of scope
> for the fix at hand. Explicit base_type(...) != PTR_TO_BTF_ID
> checks in the check_cond_jmp_op() can be removed with migration to
> reg_not_null(), but that is a behavioural change, as the special case
> would start matching for PTR_TO_BTF_ID that is also is_trusted_reg().
> I omit the behavioural change from this commit.
>
> Fixes: befae75856ab ("bpf: propagate nullness information for reg to reg comparisons")
> Suggested-by: Nicholas Carlini <npc@anthropic.com>
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>

This isn't a bug, but the changelog opens with "Nicholas Carlini
reported a bug", yet credits him only with "Suggested-by:". Would
"Reported-by: Nicholas Carlini <npc@anthropic.com>" (optionally
alongside a Closes:/Link: to the report) match the body text better?

This isn't a bug, but the reproducer is already written out in the
changelog. Would it be worth landing it as a __failure selftest
(e.g. in progs/mem_rdonly_untrusted.c next to null_check()) so the
fixed inference stays covered?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33000653934

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

* Re: [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null
  2026-08-26 18:18 [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null Eduard Zingerman
  2026-08-26 18:18 ` [PATCH bpf-next v2 2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug Eduard Zingerman
  2026-08-26 19:25 ` [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null bot+bpf-ci
@ 2026-08-27  1:50 ` patchwork-bot+netdevbpf
  2 siblings, 0 replies; 5+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-08-27  1:50 UTC (permalink / raw)
  To: Eduard Zingerman
  Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song,
	npc

Hello:

This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <ast@kernel.org>:

On Wed, 26 Aug 2026 11:18:44 -0700 you wrote:
> Nicholas Carlini reported a bug when verifier can incorrectly infer
> that a pointer is non-null. The bug occurs when two pointers are
> compared and one of them has a type w/o PTR_MAYBE_NULL flag,
> but which allows a value to be NULL at runtime.
> Here is an example:
> 
>   // `a` is PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED
>   // `a` is 0 at runtime.
>   // `b` is PTR_TO_MAP_VALUE | PTR_MAYBE_NULL
>   void *a = bpf_rdonly_cast(0, 0);
>   int  *b = bpf_map_lookup_elem(...);
> 
> [...]

Here is the summary with links:
  - [bpf-next,v2,1/2] bpf: check_cond_jmp_op(): properly infer if register is null
    https://git.kernel.org/bpf/bpf/c/d3ef6c097ba0
  - [bpf-next,v2,2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug
    https://git.kernel.org/bpf/bpf/c/ce6dcd0aed18

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2026-08-27  1:51 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-26 18:18 [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null Eduard Zingerman
2026-08-26 18:18 ` [PATCH bpf-next v2 2/2] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug Eduard Zingerman
2026-08-26 19:25   ` bot+bpf-ci
2026-08-26 19:25 ` [PATCH bpf-next v2 1/2] bpf: check_cond_jmp_op(): properly infer if register is null bot+bpf-ci
2026-08-27  1:50 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).