public inbox for bpf@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next 0/2] bpf: Fix linked register tracking
@ 2026-01-15 15:11 Puranjay Mohan
  2026-01-15 15:11 ` [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs() Puranjay Mohan
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Puranjay Mohan @ 2026-01-15 15:11 UTC (permalink / raw)
  To: bpf
  Cc: Puranjay Mohan, Puranjay Mohan, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Martin KaFai Lau,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Mykyta Yatsenko,
	kernel-team

This patch fixes the linked register tracking when multiple links from
the same register are created with a sync between the creation of these
links. The sync corrupts the id of the register and therefore the second
link is not created properly. See the patch description to understand
more.

The fix is to preserve the id while doing the sync similar to the off.

Puranjay Mohan (2):
  bpf: Preserve id of register in sync_linked_regs()
  selftests: bpf: Add test for multiple syncs from linked register

 kernel/bpf/verifier.c                         |  4 ++-
 .../bpf/progs/verifier_linked_scalars.c       | 33 +++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)


base-commit: 9a403a4aea32f1801a7f29b2385ec345d4faaf78
-- 
2.47.3


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

* [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs()
  2026-01-15 15:11 [PATCH bpf-next 0/2] bpf: Fix linked register tracking Puranjay Mohan
@ 2026-01-15 15:11 ` Puranjay Mohan
  2026-01-15 18:39   ` Eduard Zingerman
                     ` (2 more replies)
  2026-01-15 15:11 ` [PATCH bpf-next 2/2] selftests: bpf: Add test for multiple syncs from linked register Puranjay Mohan
  2026-01-16 18:30 ` [PATCH bpf-next 0/2] bpf: Fix linked register tracking patchwork-bot+netdevbpf
  2 siblings, 3 replies; 10+ messages in thread
From: Puranjay Mohan @ 2026-01-15 15:11 UTC (permalink / raw)
  To: bpf
  Cc: Puranjay Mohan, Puranjay Mohan, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Martin KaFai Lau,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Mykyta Yatsenko,
	kernel-team

sync_linked_regs() copies the id of known_reg to reg when propagating
bounds of known_reg to reg using the off of known_reg, but when
known_reg was linked to reg like:

known_reg = reg         ; both known_reg and reg get same id
known_reg += 4          ; known_reg gets off = 4, and its id gets BPF_ADD_CONST

now when a call to sync_linked_regs() happens, let's say with the following:

if known_reg >= 10 goto pc+2

known_reg's new bounds are propagated to reg but now reg gets
BPF_ADD_CONST from the copy.

This means if another link to reg is created like:

another_reg = reg       ; another_reg should get the id of reg but
                          assign_scalar_id_before_mov() sees
                          BPF_ADD_CONST on reg and assigns a new id to it.

As reg has a new id now, known_reg's link to reg is broken. If we find
new bounds for known_reg, they will not be propagated to reg.

This can be seen in the selftest added in the next commit:

0: (85) call bpf_get_prandom_u32#7    ; R0=scalar()
1: (57) r0 &= 255                     ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
2: (bf) r1 = r0                       ; R0=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R1=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
3: (07) r1 += 4                       ; R1=scalar(id=1+4,smin=umin=smin32=umin32=4,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
4: (a5) if r1 < 0xa goto pc+4         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=10,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
5: (bf) r2 = r0                       ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) R2=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255)
6: (a5) if r1 < 0xe goto pc+2         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=14,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
7: (35) if r0 >= 0xa goto pc+1        ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=9,var_off=(0x0; 0xf))
8: (37) r0 /= 0
div by zero

When 4 is verified, r1's bounds are propagated to r0 but r0 also gets
BPF_ADD_CONST (bug).
When 5 is verified, r0 gets a new id (2) and its link with r1 is broken.

After 6 we know r1 has bounds [14, 259] and therefore r0 should have
bounds [10, 255], therefore the branch at 7 is always taken. But because
r0's id was changed to 2, r1's new bounds are not propagated to r0.
The verifier still thinks r0 has bounds [6, 255] before 7 and execution
can reach div by zero.

Fix this by preserving id in sync_linked_regs() like off and subreg_def.

Fixes: 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
---
 kernel/bpf/verifier.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7a375f608263..9de0ec0c3ed9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -16871,6 +16871,7 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
 		} else {
 			s32 saved_subreg_def = reg->subreg_def;
 			s32 saved_off = reg->off;
+			u32 saved_id = reg->id;
 
 			fake_reg.type = SCALAR_VALUE;
 			__mark_reg_known(&fake_reg, (s32)reg->off - (s32)known_reg->off);
@@ -16878,10 +16879,11 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
 			/* reg = known_reg; reg += delta */
 			copy_register_state(reg, known_reg);
 			/*
-			 * Must preserve off, id and add_const flag,
+			 * Must preserve off, id and subreg_def flag,
 			 * otherwise another sync_linked_regs() will be incorrect.
 			 */
 			reg->off = saved_off;
+			reg->id = saved_id;
 			reg->subreg_def = saved_subreg_def;
 
 			scalar32_min_max_add(reg, &fake_reg);
-- 
2.47.3


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

* [PATCH bpf-next 2/2] selftests: bpf: Add test for multiple syncs from linked register
  2026-01-15 15:11 [PATCH bpf-next 0/2] bpf: Fix linked register tracking Puranjay Mohan
  2026-01-15 15:11 ` [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs() Puranjay Mohan
@ 2026-01-15 15:11 ` Puranjay Mohan
  2026-01-15 18:46   ` Eduard Zingerman
  2026-01-16 18:30 ` [PATCH bpf-next 0/2] bpf: Fix linked register tracking patchwork-bot+netdevbpf
  2 siblings, 1 reply; 10+ messages in thread
From: Puranjay Mohan @ 2026-01-15 15:11 UTC (permalink / raw)
  To: bpf
  Cc: Puranjay Mohan, Puranjay Mohan, Alexei Starovoitov,
	Andrii Nakryiko, Daniel Borkmann, Martin KaFai Lau,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Mykyta Yatsenko,
	kernel-team

Before the last commit, sync_linked_regs() corrupted the register whose
bounds are being updated by copying known_reg's id to it. The ids are
the same in value but known_reg has the BPF_ADD_CONST flag which is
wrongly copied to reg.

This later causes issues when creating new links to this reg.
assign_scalar_id_before_mov() sees this BPF_ADD_CONST and gives a new id
to this register and breaks the old links. This is exposed by the added
selftest.

Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
---
 .../bpf/progs/verifier_linked_scalars.c       | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
index 8f755d2464cf..5f41bbb730a7 100644
--- a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
+++ b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
@@ -31,4 +31,37 @@ l1:						\
 "	::: __clobber_all);
 }
 
+/*
+ * Test that sync_linked_regs() preserves register IDs.
+ *
+ * The sync_linked_regs() function copies bounds from known_reg to linked
+ * registers. When doing so, it must preserve each register's original id
+ * to allow subsequent syncs from the same source to work correctly.
+ *
+ */
+SEC("socket")
+__success
+__naked void sync_linked_regs_preserves_id(void)
+{
+	asm volatile ("						\
+	call %[bpf_get_prandom_u32];				\
+	r0 &= 0xff;	/* r0 in [0, 255] */			\
+	r1 = r0;	/* r0, r1 linked with id 1 */		\
+	r1 += 4;	/* r1 has id=1 and off=4 in [4, 259] */ \
+	if r1 < 10 goto l0_%=;					\
+	/* r1 in [10, 259], r0 synced to [6, 255] */		\
+	r2 = r0;	/* r2 has id=1 and in [6, 255] */	\
+	if r1 < 14 goto l0_%=;					\
+	/* r1 in [14, 259], r0 synced to [10, 255] */		\
+	if r0 >= 10 goto l0_%=;					\
+	/* Never executed */					\
+	r0 /= 0;						\
+l0_%=:								\
+	r0 = 0;							\
+	exit;							\
+"	:
+	: __imm(bpf_get_prandom_u32)
+	: __clobber_all);
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.47.3


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

* Re: [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs()
  2026-01-15 15:11 ` [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs() Puranjay Mohan
@ 2026-01-15 18:39   ` Eduard Zingerman
  2026-01-15 22:42   ` Andrii Nakryiko
  2026-01-16  3:08   ` Alexei Starovoitov
  2 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2026-01-15 18:39 UTC (permalink / raw)
  To: Puranjay Mohan, bpf
  Cc: Puranjay Mohan, Alexei Starovoitov, Andrii Nakryiko,
	Daniel Borkmann, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
	Mykyta Yatsenko, kernel-team

On Thu, 2026-01-15 at 07:11 -0800, Puranjay Mohan wrote:
> sync_linked_regs() copies the id of known_reg to reg when propagating
> bounds of known_reg to reg using the off of known_reg, but when
> known_reg was linked to reg like:
> 
> known_reg = reg         ; both known_reg and reg get same id
> known_reg += 4          ; known_reg gets off = 4, and its id gets BPF_ADD_CONST
> 
> now when a call to sync_linked_regs() happens, let's say with the following:
> 
> if known_reg >= 10 goto pc+2
> 
> known_reg's new bounds are propagated to reg but now reg gets
> BPF_ADD_CONST from the copy.
> 
> This means if another link to reg is created like:
> 
> another_reg = reg       ; another_reg should get the id of reg but
>                           assign_scalar_id_before_mov() sees
>                           BPF_ADD_CONST on reg and assigns a new id to it.
> 
> As reg has a new id now, known_reg's link to reg is broken. If we find
> new bounds for known_reg, they will not be propagated to reg.
> 
> This can be seen in the selftest added in the next commit:
> 
> 0: (85) call bpf_get_prandom_u32#7    ; R0=scalar()
> 1: (57) r0 &= 255                     ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> 2: (bf) r1 = r0                       ; R0=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R1=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> 3: (07) r1 += 4                       ; R1=scalar(id=1+4,smin=umin=smin32=umin32=4,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 4: (a5) if r1 < 0xa goto pc+4         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=10,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 5: (bf) r2 = r0                       ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) R2=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255)
> 6: (a5) if r1 < 0xe goto pc+2         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=14,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 7: (35) if r0 >= 0xa goto pc+1        ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=9,var_off=(0x0; 0xf))
> 8: (37) r0 /= 0
> div by zero
> 
> When 4 is verified, r1's bounds are propagated to r0 but r0 also gets
> BPF_ADD_CONST (bug).
> When 5 is verified, r0 gets a new id (2) and its link with r1 is broken.
> 
> After 6 we know r1 has bounds [14, 259] and therefore r0 should have
> bounds [10, 255], therefore the branch at 7 is always taken. But because
> r0's id was changed to 2, r1's new bounds are not propagated to r0.
> The verifier still thinks r0 has bounds [6, 255] before 7 and execution
> can reach div by zero.
> 
> Fix this by preserving id in sync_linked_regs() like off and subreg_def.
> 
> Fixes: 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")
> Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
> ---

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

[...]

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

* Re: [PATCH bpf-next 2/2] selftests: bpf: Add test for multiple syncs from linked register
  2026-01-15 15:11 ` [PATCH bpf-next 2/2] selftests: bpf: Add test for multiple syncs from linked register Puranjay Mohan
@ 2026-01-15 18:46   ` Eduard Zingerman
  0 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2026-01-15 18:46 UTC (permalink / raw)
  To: Puranjay Mohan, bpf
  Cc: Puranjay Mohan, Alexei Starovoitov, Andrii Nakryiko,
	Daniel Borkmann, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
	Mykyta Yatsenko, kernel-team

On Thu, 2026-01-15 at 07:11 -0800, Puranjay Mohan wrote:
> Before the last commit, sync_linked_regs() corrupted the register whose
> bounds are being updated by copying known_reg's id to it. The ids are
> the same in value but known_reg has the BPF_ADD_CONST flag which is
> wrongly copied to reg.
> 
> This later causes issues when creating new links to this reg.
> assign_scalar_id_before_mov() sees this BPF_ADD_CONST and gives a new id
> to this register and breaks the old links. This is exposed by the added
> selftest.
> 
> Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
> ---

Confirm the test behavior on master vs master+patch #1.

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

[...]

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

* Re: [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs()
  2026-01-15 15:11 ` [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs() Puranjay Mohan
  2026-01-15 18:39   ` Eduard Zingerman
@ 2026-01-15 22:42   ` Andrii Nakryiko
  2026-01-16 12:59     ` Puranjay Mohan
  2026-01-16  3:08   ` Alexei Starovoitov
  2 siblings, 1 reply; 10+ messages in thread
From: Andrii Nakryiko @ 2026-01-15 22:42 UTC (permalink / raw)
  To: Puranjay Mohan
  Cc: bpf, Puranjay Mohan, Alexei Starovoitov, Andrii Nakryiko,
	Daniel Borkmann, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Mykyta Yatsenko, kernel-team

On Thu, Jan 15, 2026 at 7:11 AM Puranjay Mohan <puranjay@kernel.org> wrote:
>
> sync_linked_regs() copies the id of known_reg to reg when propagating
> bounds of known_reg to reg using the off of known_reg, but when
> known_reg was linked to reg like:
>
> known_reg = reg         ; both known_reg and reg get same id
> known_reg += 4          ; known_reg gets off = 4, and its id gets BPF_ADD_CONST
>
> now when a call to sync_linked_regs() happens, let's say with the following:
>
> if known_reg >= 10 goto pc+2
>
> known_reg's new bounds are propagated to reg but now reg gets
> BPF_ADD_CONST from the copy.
>
> This means if another link to reg is created like:
>
> another_reg = reg       ; another_reg should get the id of reg but
>                           assign_scalar_id_before_mov() sees
>                           BPF_ADD_CONST on reg and assigns a new id to it.
>
> As reg has a new id now, known_reg's link to reg is broken. If we find
> new bounds for known_reg, they will not be propagated to reg.
>
> This can be seen in the selftest added in the next commit:
>
> 0: (85) call bpf_get_prandom_u32#7    ; R0=scalar()
> 1: (57) r0 &= 255                     ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> 2: (bf) r1 = r0                       ; R0=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R1=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> 3: (07) r1 += 4                       ; R1=scalar(id=1+4,smin=umin=smin32=umin32=4,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 4: (a5) if r1 < 0xa goto pc+4         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=10,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 5: (bf) r2 = r0                       ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) R2=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255)
> 6: (a5) if r1 < 0xe goto pc+2         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=14,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 7: (35) if r0 >= 0xa goto pc+1        ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=9,var_off=(0x0; 0xf))
> 8: (37) r0 /= 0
> div by zero
>
> When 4 is verified, r1's bounds are propagated to r0 but r0 also gets
> BPF_ADD_CONST (bug).
> When 5 is verified, r0 gets a new id (2) and its link with r1 is broken.
>
> After 6 we know r1 has bounds [14, 259] and therefore r0 should have
> bounds [10, 255], therefore the branch at 7 is always taken. But because
> r0's id was changed to 2, r1's new bounds are not propagated to r0.
> The verifier still thinks r0 has bounds [6, 255] before 7 and execution
> can reach div by zero.
>
> Fix this by preserving id in sync_linked_regs() like off and subreg_def.

We should mark_reg_scratched() all the registers that got new IDs or
new ranges or offsets. Basically, if anything about register state was
changed, even if verified instruction doesn't work with that register
directly, all affected registers (we can think about this as side
effects) should be "scratched" and emitted in the verifier log.

>
> Fixes: 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")
> Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
> ---
>  kernel/bpf/verifier.c | 4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 7a375f608263..9de0ec0c3ed9 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -16871,6 +16871,7 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
>                 } else {
>                         s32 saved_subreg_def = reg->subreg_def;
>                         s32 saved_off = reg->off;
> +                       u32 saved_id = reg->id;
>
>                         fake_reg.type = SCALAR_VALUE;
>                         __mark_reg_known(&fake_reg, (s32)reg->off - (s32)known_reg->off);
> @@ -16878,10 +16879,11 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
>                         /* reg = known_reg; reg += delta */
>                         copy_register_state(reg, known_reg);
>                         /*
> -                        * Must preserve off, id and add_const flag,
> +                        * Must preserve off, id and subreg_def flag,
>                          * otherwise another sync_linked_regs() will be incorrect.
>                          */
>                         reg->off = saved_off;
> +                       reg->id = saved_id;
>                         reg->subreg_def = saved_subreg_def;
>
>                         scalar32_min_max_add(reg, &fake_reg);
> --
> 2.47.3
>

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

* Re: [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs()
  2026-01-15 15:11 ` [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs() Puranjay Mohan
  2026-01-15 18:39   ` Eduard Zingerman
  2026-01-15 22:42   ` Andrii Nakryiko
@ 2026-01-16  3:08   ` Alexei Starovoitov
  2026-01-16 13:01     ` Puranjay Mohan
  2 siblings, 1 reply; 10+ messages in thread
From: Alexei Starovoitov @ 2026-01-16  3:08 UTC (permalink / raw)
  To: Puranjay Mohan, Ihor Solodrai
  Cc: bpf, Puranjay Mohan, Alexei Starovoitov, Andrii Nakryiko,
	Daniel Borkmann, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Mykyta Yatsenko, Kernel Team

On Thu, Jan 15, 2026 at 7:11 AM Puranjay Mohan <puranjay@kernel.org> wrote:
>
> sync_linked_regs() copies the id of known_reg to reg when propagating
> bounds of known_reg to reg using the off of known_reg, but when
> known_reg was linked to reg like:
>
> known_reg = reg         ; both known_reg and reg get same id
> known_reg += 4          ; known_reg gets off = 4, and its id gets BPF_ADD_CONST
>
> now when a call to sync_linked_regs() happens, let's say with the following:
>
> if known_reg >= 10 goto pc+2
>
> known_reg's new bounds are propagated to reg but now reg gets
> BPF_ADD_CONST from the copy.
>
> This means if another link to reg is created like:
>
> another_reg = reg       ; another_reg should get the id of reg but
>                           assign_scalar_id_before_mov() sees
>                           BPF_ADD_CONST on reg and assigns a new id to it.
>
> As reg has a new id now, known_reg's link to reg is broken. If we find
> new bounds for known_reg, they will not be propagated to reg.
>
> This can be seen in the selftest added in the next commit:
>
> 0: (85) call bpf_get_prandom_u32#7    ; R0=scalar()
> 1: (57) r0 &= 255                     ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> 2: (bf) r1 = r0                       ; R0=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R1=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> 3: (07) r1 += 4                       ; R1=scalar(id=1+4,smin=umin=smin32=umin32=4,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 4: (a5) if r1 < 0xa goto pc+4         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=10,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 5: (bf) r2 = r0                       ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) R2=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255)
> 6: (a5) if r1 < 0xe goto pc+2         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=14,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> 7: (35) if r0 >= 0xa goto pc+1        ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=9,var_off=(0x0; 0xf))
> 8: (37) r0 /= 0
> div by zero
>
> When 4 is verified, r1's bounds are propagated to r0 but r0 also gets
> BPF_ADD_CONST (bug).
> When 5 is verified, r0 gets a new id (2) and its link with r1 is broken.
>
> After 6 we know r1 has bounds [14, 259] and therefore r0 should have
> bounds [10, 255], therefore the branch at 7 is always taken. But because
> r0's id was changed to 2, r1's new bounds are not propagated to r0.
> The verifier still thinks r0 has bounds [6, 255] before 7 and execution
> can reach div by zero.
>
> Fix this by preserving id in sync_linked_regs() like off and subreg_def.
>
> Fixes: 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")
> Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
> ---
>  kernel/bpf/verifier.c | 4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 7a375f608263..9de0ec0c3ed9 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -16871,6 +16871,7 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
>                 } else {
>                         s32 saved_subreg_def = reg->subreg_def;
>                         s32 saved_off = reg->off;
> +                       u32 saved_id = reg->id;
>
>                         fake_reg.type = SCALAR_VALUE;
>                         __mark_reg_known(&fake_reg, (s32)reg->off - (s32)known_reg->off);
> @@ -16878,10 +16879,11 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
>                         /* reg = known_reg; reg += delta */
>                         copy_register_state(reg, known_reg);
>                         /*
> -                        * Must preserve off, id and add_const flag,
> +                        * Must preserve off, id and subreg_def flag,
>                          * otherwise another sync_linked_regs() will be incorrect.
>                          */
>                         reg->off = saved_off;
> +                       reg->id = saved_id;

What is the veristat difference for meta/scx ?

I don't trust CI at the moment:

026-01-16T01:26:34.7163235Z Failed to open 'c-scheds_lib_sdt_task.bpf.o': -2
2026-01-16T01:26:34.7240161Z Failed to open
'c-scheds_scheds_c_scx_sdt.bpf.o': -2
2026-01-16T01:26:34.7298286Z Failed to open
'rust-scheds_scx_arena-9355999175dda454_out_arena.bpf.o': -2
2026-01-16T01:26:34.7353438Z Failed to open
'rust-scheds_scx_arena-9355999175dda454_out_atq.bpf.o': -2
2026-01-16T01:26:34.7414081Z Failed to open
'rust-scheds_scx_arena-9355999175dda454_out_bitmap.bpf.o': -2
...

2026-01-16T01:27:52.3087135Z # No changes in verification performance

something fishy.

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

* Re: [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs()
  2026-01-15 22:42   ` Andrii Nakryiko
@ 2026-01-16 12:59     ` Puranjay Mohan
  0 siblings, 0 replies; 10+ messages in thread
From: Puranjay Mohan @ 2026-01-16 12:59 UTC (permalink / raw)
  To: Andrii Nakryiko
  Cc: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
	Mykyta Yatsenko, kernel-team

On Thu, Jan 15, 2026 at 11:43 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Thu, Jan 15, 2026 at 7:11 AM Puranjay Mohan <puranjay@kernel.org> wrote:
> >
> > sync_linked_regs() copies the id of known_reg to reg when propagating
> > bounds of known_reg to reg using the off of known_reg, but when
> > known_reg was linked to reg like:
> >
> > known_reg = reg         ; both known_reg and reg get same id
> > known_reg += 4          ; known_reg gets off = 4, and its id gets BPF_ADD_CONST
> >
> > now when a call to sync_linked_regs() happens, let's say with the following:
> >
> > if known_reg >= 10 goto pc+2
> >
> > known_reg's new bounds are propagated to reg but now reg gets
> > BPF_ADD_CONST from the copy.
> >
> > This means if another link to reg is created like:
> >
> > another_reg = reg       ; another_reg should get the id of reg but
> >                           assign_scalar_id_before_mov() sees
> >                           BPF_ADD_CONST on reg and assigns a new id to it.
> >
> > As reg has a new id now, known_reg's link to reg is broken. If we find
> > new bounds for known_reg, they will not be propagated to reg.
> >
> > This can be seen in the selftest added in the next commit:
> >
> > 0: (85) call bpf_get_prandom_u32#7    ; R0=scalar()
> > 1: (57) r0 &= 255                     ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> > 2: (bf) r1 = r0                       ; R0=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R1=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> > 3: (07) r1 += 4                       ; R1=scalar(id=1+4,smin=umin=smin32=umin32=4,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> > 4: (a5) if r1 < 0xa goto pc+4         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=10,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> > 5: (bf) r2 = r0                       ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) R2=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255)
> > 6: (a5) if r1 < 0xe goto pc+2         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=14,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> > 7: (35) if r0 >= 0xa goto pc+1        ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=9,var_off=(0x0; 0xf))
> > 8: (37) r0 /= 0
> > div by zero
> >
> > When 4 is verified, r1's bounds are propagated to r0 but r0 also gets
> > BPF_ADD_CONST (bug).
> > When 5 is verified, r0 gets a new id (2) and its link with r1 is broken.
> >
> > After 6 we know r1 has bounds [14, 259] and therefore r0 should have
> > bounds [10, 255], therefore the branch at 7 is always taken. But because
> > r0's id was changed to 2, r1's new bounds are not propagated to r0.
> > The verifier still thinks r0 has bounds [6, 255] before 7 and execution
> > can reach div by zero.
> >
> > Fix this by preserving id in sync_linked_regs() like off and subreg_def.
>
> We should mark_reg_scratched() all the registers that got new IDs or
> new ranges or offsets. Basically, if anything about register state was
> changed, even if verified instruction doesn't work with that register
> directly, all affected registers (we can think about this as side
> effects) should be "scratched" and emitted in the verifier log.
>

I agree, will send a separate patch for this.

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

* Re: [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs()
  2026-01-16  3:08   ` Alexei Starovoitov
@ 2026-01-16 13:01     ` Puranjay Mohan
  0 siblings, 0 replies; 10+ messages in thread
From: Puranjay Mohan @ 2026-01-16 13:01 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Ihor Solodrai, bpf, Alexei Starovoitov, Andrii Nakryiko,
	Daniel Borkmann, Martin KaFai Lau, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Mykyta Yatsenko, Kernel Team

On Fri, Jan 16, 2026 at 4:09 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Thu, Jan 15, 2026 at 7:11 AM Puranjay Mohan <puranjay@kernel.org> wrote:
> >
> > sync_linked_regs() copies the id of known_reg to reg when propagating
> > bounds of known_reg to reg using the off of known_reg, but when
> > known_reg was linked to reg like:
> >
> > known_reg = reg         ; both known_reg and reg get same id
> > known_reg += 4          ; known_reg gets off = 4, and its id gets BPF_ADD_CONST
> >
> > now when a call to sync_linked_regs() happens, let's say with the following:
> >
> > if known_reg >= 10 goto pc+2
> >
> > known_reg's new bounds are propagated to reg but now reg gets
> > BPF_ADD_CONST from the copy.
> >
> > This means if another link to reg is created like:
> >
> > another_reg = reg       ; another_reg should get the id of reg but
> >                           assign_scalar_id_before_mov() sees
> >                           BPF_ADD_CONST on reg and assigns a new id to it.
> >
> > As reg has a new id now, known_reg's link to reg is broken. If we find
> > new bounds for known_reg, they will not be propagated to reg.
> >
> > This can be seen in the selftest added in the next commit:
> >
> > 0: (85) call bpf_get_prandom_u32#7    ; R0=scalar()
> > 1: (57) r0 &= 255                     ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> > 2: (bf) r1 = r0                       ; R0=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R1=scalar(id=1,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))
> > 3: (07) r1 += 4                       ; R1=scalar(id=1+4,smin=umin=smin32=umin32=4,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> > 4: (a5) if r1 < 0xa goto pc+4         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=10,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> > 5: (bf) r2 = r0                       ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255) R2=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=255)
> > 6: (a5) if r1 < 0xe goto pc+2         ; R1=scalar(id=1+4,smin=umin=smin32=umin32=14,smax=umax=smax32=umax32=259,var_off=(0x0; 0x1ff))
> > 7: (35) if r0 >= 0xa goto pc+1        ; R0=scalar(id=2,smin=umin=smin32=umin32=6,smax=umax=smax32=umax32=9,var_off=(0x0; 0xf))
> > 8: (37) r0 /= 0
> > div by zero
> >
> > When 4 is verified, r1's bounds are propagated to r0 but r0 also gets
> > BPF_ADD_CONST (bug).
> > When 5 is verified, r0 gets a new id (2) and its link with r1 is broken.
> >
> > After 6 we know r1 has bounds [14, 259] and therefore r0 should have
> > bounds [10, 255], therefore the branch at 7 is always taken. But because
> > r0's id was changed to 2, r1's new bounds are not propagated to r0.
> > The verifier still thinks r0 has bounds [6, 255] before 7 and execution
> > can reach div by zero.
> >
> > Fix this by preserving id in sync_linked_regs() like off and subreg_def.
> >
> > Fixes: 98d7ca374ba4 ("bpf: Track delta between "linked" registers.")
> > Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
> > ---
> >  kernel/bpf/verifier.c | 4 +++-
> >  1 file changed, 3 insertions(+), 1 deletion(-)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 7a375f608263..9de0ec0c3ed9 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -16871,6 +16871,7 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
> >                 } else {
> >                         s32 saved_subreg_def = reg->subreg_def;
> >                         s32 saved_off = reg->off;
> > +                       u32 saved_id = reg->id;
> >
> >                         fake_reg.type = SCALAR_VALUE;
> >                         __mark_reg_known(&fake_reg, (s32)reg->off - (s32)known_reg->off);
> > @@ -16878,10 +16879,11 @@ static void sync_linked_regs(struct bpf_verifier_state *vstate, struct bpf_reg_s
> >                         /* reg = known_reg; reg += delta */
> >                         copy_register_state(reg, known_reg);
> >                         /*
> > -                        * Must preserve off, id and add_const flag,
> > +                        * Must preserve off, id and subreg_def flag,
> >                          * otherwise another sync_linked_regs() will be incorrect.
> >                          */
> >                         reg->off = saved_off;
> > +                       reg->id = saved_id;
>
> What is the veristat difference for meta/scx ?
>
> I don't trust CI at the moment:
>
> 026-01-16T01:26:34.7163235Z Failed to open 'c-scheds_lib_sdt_task.bpf.o': -2
> 2026-01-16T01:26:34.7240161Z Failed to open
> 'c-scheds_scheds_c_scx_sdt.bpf.o': -2
> 2026-01-16T01:26:34.7298286Z Failed to open
> 'rust-scheds_scx_arena-9355999175dda454_out_arena.bpf.o': -2
> 2026-01-16T01:26:34.7353438Z Failed to open
> 'rust-scheds_scx_arena-9355999175dda454_out_atq.bpf.o': -2
> 2026-01-16T01:26:34.7414081Z Failed to open
> 'rust-scheds_scx_arena-9355999175dda454_out_bitmap.bpf.o': -2
> ...
>
> 2026-01-16T01:27:52.3087135Z # No changes in verification performance
>
> something fishy.


I don't see any change in veristat output:

../../../veristat/src/veristat -C -e file,prog,states,insns -f
"insns_pct>0" fb_before.csv fb_after.csv
File  Program  States (A)  States (B)  States (DIFF)  Insns (A)  Insns
(B)  Insns (DIFF)
----  -------  ----------  ----------  -------------  ---------
---------  ------------


../../../veristat/src/veristat -C -e file,prog,states,insns -f
"insns_pct>0" scx_before.csv scx_after.csv
File  Program  States (A)  States (B)  States (DIFF)  Insns (A)  Insns
(B)  Insns (DIFF)
----  -------  ----------  ----------  -------------  ---------
---------  ------------

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

* Re: [PATCH bpf-next 0/2] bpf: Fix linked register tracking
  2026-01-15 15:11 [PATCH bpf-next 0/2] bpf: Fix linked register tracking Puranjay Mohan
  2026-01-15 15:11 ` [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs() Puranjay Mohan
  2026-01-15 15:11 ` [PATCH bpf-next 2/2] selftests: bpf: Add test for multiple syncs from linked register Puranjay Mohan
@ 2026-01-16 18:30 ` patchwork-bot+netdevbpf
  2 siblings, 0 replies; 10+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-01-16 18:30 UTC (permalink / raw)
  To: Puranjay Mohan
  Cc: bpf, puranjay12, ast, andrii, daniel, martin.lau, eddyz87, memxor,
	mykyta.yatsenko5, kernel-team

Hello:

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

On Thu, 15 Jan 2026 07:11:39 -0800 you wrote:
> This patch fixes the linked register tracking when multiple links from
> the same register are created with a sync between the creation of these
> links. The sync corrupts the id of the register and therefore the second
> link is not created properly. See the patch description to understand
> more.
> 
> The fix is to preserve the id while doing the sync similar to the off.
> 
> [...]

Here is the summary with links:
  - [bpf-next,1/2] bpf: Preserve id of register in sync_linked_regs()
    https://git.kernel.org/bpf/bpf-next/c/af9e89d8dd39
  - [bpf-next,2/2] selftests: bpf: Add test for multiple syncs from linked register
    https://git.kernel.org/bpf/bpf-next/c/086c99fbe450

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] 10+ messages in thread

end of thread, other threads:[~2026-01-16 18:33 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-15 15:11 [PATCH bpf-next 0/2] bpf: Fix linked register tracking Puranjay Mohan
2026-01-15 15:11 ` [PATCH bpf-next 1/2] bpf: Preserve id of register in sync_linked_regs() Puranjay Mohan
2026-01-15 18:39   ` Eduard Zingerman
2026-01-15 22:42   ` Andrii Nakryiko
2026-01-16 12:59     ` Puranjay Mohan
2026-01-16  3:08   ` Alexei Starovoitov
2026-01-16 13:01     ` Puranjay Mohan
2026-01-15 15:11 ` [PATCH bpf-next 2/2] selftests: bpf: Add test for multiple syncs from linked register Puranjay Mohan
2026-01-15 18:46   ` Eduard Zingerman
2026-01-16 18:30 ` [PATCH bpf-next 0/2] bpf: Fix linked register tracking 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