* [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR
@ 2026-03-14 2:15 Daniel Wade
2026-03-14 2:15 ` [PATCH bpf v4 1/2] bpf: Fix unsound scalar forking in maybe_fork_scalars() " Daniel Wade
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Daniel Wade @ 2026-03-14 2:15 UTC (permalink / raw)
To: bpf; +Cc: daniel, eddyz87, ameryhung, ast, danjwade95
maybe_fork_scalars() unconditionally sets the pushed path dst register
to 0 for both BPF_AND and BPF_OR. For AND this is correct (0 & K == 0),
but for OR it is wrong (0 | K == K, not 0). This causes the verifier to
track an incorrect value on the pushed path, leading to a verifier/runtime
divergence that allows out-of-bounds map value access.
v4: Use block comment style for multi-line comments in selftests (Amery Hung)
Add Reviewed-by/Acked-by tags
v3: Use single-line comment style in selftests (Alexei Starovoitov)
v2: Use push_stack(env, env->insn_idx, ...) to re-execute the insn
on the pushed path (Eduard Zingerman)
Daniel Wade (2):
bpf: Fix unsound scalar forking in maybe_fork_scalars() for BPF_OR
selftests/bpf: Add tests for maybe_fork_scalars() OR vs AND handling
kernel/bpf/verifier.c | 2 +-
.../selftests/bpf/progs/verifier_bounds.c | 94 +++++++++++++++++++
2 files changed, 95 insertions(+), 1 deletion(-)
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH bpf v4 1/2] bpf: Fix unsound scalar forking in maybe_fork_scalars() for BPF_OR
2026-03-14 2:15 [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR Daniel Wade
@ 2026-03-14 2:15 ` Daniel Wade
2026-03-14 2:15 ` [PATCH bpf v4 2/2] selftests/bpf: Add tests for maybe_fork_scalars() OR vs AND handling Daniel Wade
2026-03-21 20:20 ` [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR patchwork-bot+netdevbpf
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Wade @ 2026-03-14 2:15 UTC (permalink / raw)
To: bpf; +Cc: daniel, eddyz87, ameryhung, ast, danjwade95
maybe_fork_scalars() is called for both BPF_AND and BPF_OR when the
source operand is a constant. When dst has signed range [-1, 0], it
forks the verifier state: the pushed path gets dst = 0, the current
path gets dst = -1.
For BPF_AND this is correct: 0 & K == 0.
For BPF_OR this is wrong: 0 | K == K, not 0.
The pushed path therefore tracks dst as 0 when the runtime value is K,
producing an exploitable verifier/runtime divergence that allows
out-of-bounds map access.
Fix this by passing env->insn_idx (instead of env->insn_idx + 1) to
push_stack(), so the pushed path re-executes the ALU instruction with
dst = 0 and naturally computes the correct result for any opcode.
Fixes: bffacdb80b93 ("bpf: Recognize special arithmetic shift in the verifier")
Signed-off-by: Daniel Wade <danjwade95@gmail.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
---
kernel/bpf/verifier.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index a9e2380327..68fa308520 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -14206,7 +14206,7 @@ static int maybe_fork_scalars(struct bpf_verifier_env *env, struct bpf_insn *ins
else
return 0;
- branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false);
+ branch = push_stack(env, env->insn_idx, env->insn_idx, false);
if (IS_ERR(branch))
return PTR_ERR(branch);
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH bpf v4 2/2] selftests/bpf: Add tests for maybe_fork_scalars() OR vs AND handling
2026-03-14 2:15 [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR Daniel Wade
2026-03-14 2:15 ` [PATCH bpf v4 1/2] bpf: Fix unsound scalar forking in maybe_fork_scalars() " Daniel Wade
@ 2026-03-14 2:15 ` Daniel Wade
2026-03-21 20:20 ` [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR patchwork-bot+netdevbpf
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Wade @ 2026-03-14 2:15 UTC (permalink / raw)
To: bpf; +Cc: daniel, eddyz87, ameryhung, ast, danjwade95
Add three test cases to verifier_bounds.c to verify that
maybe_fork_scalars() correctly tracks register values for BPF_OR
operations with constant source operands:
1. or_scalar_fork_rejects_oob: After ARSH 63 + OR 8, the pushed
path should have dst = 8. With value_size = 8, accessing
map_value + 8 is out of bounds and must be rejected.
2. and_scalar_fork_still_works: Regression test ensuring AND
forking continues to work. ARSH 63 + AND 4 produces pushed
dst = 0 and current dst = 4, both within value_size = 8.
3. or_scalar_fork_allows_inbounds: After ARSH 63 + OR 4, the
pushed path has dst = 4, which is within value_size = 8
and should be accepted.
These tests exercise the fix in the previous patch, which makes the
pushed path re-execute the ALU instruction so it computes the correct
result for BPF_OR.
Signed-off-by: Daniel Wade <danjwade95@gmail.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
---
.../selftests/bpf/progs/verifier_bounds.c | 94 +++++++++++++++++++
1 file changed, 94 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds.c b/tools/testing/selftests/bpf/progs/verifier_bounds.c
index a0bb7fb40e..d8fe6f98e7 100644
--- a/tools/testing/selftests/bpf/progs/verifier_bounds.c
+++ b/tools/testing/selftests/bpf/progs/verifier_bounds.c
@@ -1200,4 +1200,98 @@ l0_%=: r0 = 0; \
: __clobber_all);
}
+SEC("socket")
+__description("maybe_fork_scalars: OR with constant rejects OOB")
+__failure __msg("invalid access to map value")
+__naked void or_scalar_fork_rejects_oob(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ *(u64*)(r10 - 8) = r1; \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = %[map_hash_8b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r9 = r0; \
+ r6 = *(u64*)(r9 + 0); \
+ r6 s>>= 63; \
+ r6 |= 8; \
+ /* r6 is -1 (current) or 8 (pushed) */ \
+ if r6 s< 0 goto l0_%=; \
+ /* pushed path: r6 = 8, OOB for value_size=8 */ \
+ r9 += r6; \
+ r0 = *(u8*)(r9 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("maybe_fork_scalars: AND with constant still works")
+__success __retval(0)
+__naked void and_scalar_fork_still_works(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ *(u64*)(r10 - 8) = r1; \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = %[map_hash_8b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r9 = r0; \
+ r6 = *(u64*)(r9 + 0); \
+ r6 s>>= 63; \
+ r6 &= 4; \
+ /* \
+ * r6 is 0 (pushed, 0&4==0) or 4 (current) \
+ * both within value_size=8 \
+ */ \
+ if r6 s< 0 goto l0_%=; \
+ r9 += r6; \
+ r0 = *(u8*)(r9 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("maybe_fork_scalars: OR with constant allows in-bounds")
+__success __retval(0)
+__naked void or_scalar_fork_allows_inbounds(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ *(u64*)(r10 - 8) = r1; \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = %[map_hash_8b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r9 = r0; \
+ r6 = *(u64*)(r9 + 0); \
+ r6 s>>= 63; \
+ r6 |= 4; \
+ /* \
+ * r6 is -1 (current) or 4 (pushed) \
+ * pushed path: r6 = 4, within value_size=8 \
+ */ \
+ if r6 s< 0 goto l0_%=; \
+ r9 += r6; \
+ r0 = *(u8*)(r9 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR
2026-03-14 2:15 [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR Daniel Wade
2026-03-14 2:15 ` [PATCH bpf v4 1/2] bpf: Fix unsound scalar forking in maybe_fork_scalars() " Daniel Wade
2026-03-14 2:15 ` [PATCH bpf v4 2/2] selftests/bpf: Add tests for maybe_fork_scalars() OR vs AND handling Daniel Wade
@ 2026-03-21 20:20 ` patchwork-bot+netdevbpf
2 siblings, 0 replies; 4+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-03-21 20:20 UTC (permalink / raw)
To: Daniel Wade; +Cc: bpf, daniel, eddyz87, ameryhung, ast
Hello:
This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <ast@kernel.org>:
On Sat, 14 Mar 2026 13:15:19 +1100 you wrote:
> maybe_fork_scalars() unconditionally sets the pushed path dst register
> to 0 for both BPF_AND and BPF_OR. For AND this is correct (0 & K == 0),
> but for OR it is wrong (0 | K == K, not 0). This causes the verifier to
> track an incorrect value on the pushed path, leading to a verifier/runtime
> divergence that allows out-of-bounds map value access.
>
> v4: Use block comment style for multi-line comments in selftests (Amery Hung)
> Add Reviewed-by/Acked-by tags
> v3: Use single-line comment style in selftests (Alexei Starovoitov)
> v2: Use push_stack(env, env->insn_idx, ...) to re-execute the insn
> on the pushed path (Eduard Zingerman)
>
> [...]
Here is the summary with links:
- [bpf,v4,1/2] bpf: Fix unsound scalar forking in maybe_fork_scalars() for BPF_OR
https://git.kernel.org/bpf/bpf/c/c845894ebd6f
- [bpf,v4,2/2] selftests/bpf: Add tests for maybe_fork_scalars() OR vs AND handling
https://git.kernel.org/bpf/bpf/c/0ad1734cc559
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] 4+ messages in thread
end of thread, other threads:[~2026-03-21 20:20 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-14 2:15 [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR Daniel Wade
2026-03-14 2:15 ` [PATCH bpf v4 1/2] bpf: Fix unsound scalar forking in maybe_fork_scalars() " Daniel Wade
2026-03-14 2:15 ` [PATCH bpf v4 2/2] selftests/bpf: Add tests for maybe_fork_scalars() OR vs AND handling Daniel Wade
2026-03-21 20:20 ` [PATCH bpf v4 0/2] bpf: Fix unsound scalar forking for BPF_OR patchwork-bot+netdevbpf
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.