* [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).