Netdev List
 help / color / mirror / Atom feed
* [PATCH stable 6.6.y v2 0/3] bpf: backport scalar not-equal tracking fixes
@ 2026-06-07 17:09 Zhenzhong Wu
  2026-06-07 17:09 ` [PATCH stable 6.6.y v2 1/3] bpf: drop knowledge-losing __reg_combine_{32,64}_into_{64,32} logic Zhenzhong Wu
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Zhenzhong Wu @ 2026-06-07 17:09 UTC (permalink / raw)
  To: bpf
  Cc: netdev, linux-kernel, ast, daniel, john.fastabend, andrii,
	martin.lau, song, yonghong.song, kpsingh, sdf, haoluo, jolsa,
	menglong8.dong, eddyz87, shung-hsi.yu, stable, mykolal, tamird

Hi,

This series backports two BPF verifier scalar range-tracking fixes to
6.6.y and adds a selftest. It fixes a verifier state-pruning issue where
an impossible linked-scalar path can be kept while the real success path is
pruned.

The issue is verifier scalar state tracking, not helper-specific behavior.
A helper return value in r0 and another scalar can become linked by scalar
id on one branch. If the verifier does not preserve the not-equal fact on
the right branch edge, a later check can let it explore an impossible
continuation, narrow the linked scalar to the wrong value, and prune the
real success path against an earlier cached state. The program is accepted
by the verifier but then reports the wrong branch outcome at runtime.

The original visible failure was found in Rust-generated eBPF around helper
calls. Rust match lowering can keep a helper return value and a scalar
filled through a by-reference helper argument in the same enum-style control
flow. That makes it easy for the verifier-visible scalar values to become
linked by scalar id.

The relevant verifier-log bytecode from the original fexit reproducer is
below. The later instructions only store r7 into a map so user space can
observe which branch the verifier kept.

  15: (85) call bpf_get_func_ret#184    ; R0_w=scalar() fp-8_w=mmmmmmmm
  16: (79) r7 = *(u64 *)(r10 -8)        ; R7_w=scalar() R10=fp0
  17: (15) if r0 == 0x0 goto pc+1       ; R0_w=scalar()
  18: (bf) r7 = r0                      ; R0=scalar(id=1) R7=scalar(id=1)
  19: (55) if r0 != 0x0 goto pc+6       ; R0=0
  20: (67) r7 <<= 32                    ; R7_w=0
  21: (77) r7 >>= 32                    ; R7_w=0
  22: (b7) r1 = 1                       ; R1_w=1
  23: (55) if r7 != 0xf goto pc+1

The failure mechanism is:

  1. The program checks "if r0 == 0". The jump target is the success path,
     and the fallthrough path is the failure path and should imply r0 != 0.

  2. On affected kernels, the verifier does not record that r0 != 0 fact for
     the fallthrough path. The following "r7 = r0" then gives r0 and r7 the
     same scalar id while both are still treated as possibly zero.

  3. At the later "if r0 != 0" check, the verifier still thinks r0 may be
     zero, so it explores the fallthrough path of that JNE. That path means
     r0 == 0, and because r7 shares the same scalar id, r7 is narrowed to
     zero as well. This is an impossible path: it came from the earlier
     failure path that should have implied r0 != 0.

  4. That impossible continuation reaches the return-value comparison with
     r7 == 0 and can make the verifier keep only the wrong branch. When the
     real success path is analyzed later, state pruning considers it safe
     against the earlier cached verifier state, so the real continuation is
     not explored.

The relevant pruning point is that regsafe()/states_equal() accepted the
real success-path state against an earlier cached state where r0 was an
imprecise scalar and r7 constraints were loose enough to cover the current
r7.

After confirming the mechanism, I used a reproducer with the same verifier
state shape, now captured by the selftest, as the test case for git bisect.
The bisect started from the affected 6.7.y behavior and the fixed v6.8
behavior, and narrowed the fix to the v6.7..v6.8 window. It identified the
upstream fix as:

  d028f87517d6775dccff4ddbca2740826f9e53f1
  bpf: make the verifier tracks the "not equal" for regs

For 6.6.y and older stable verifier code, applying d028f87517d6 alone is
not sufficient. The verifier also needs the range-preservation semantics
from:

  9e314f5d8682e1fe6ac214fb34580a238b6fd3c4
  bpf: drop knowledge-losing __reg_combine_{32,64}_into_{64,32} logic

Without that semantic prerequisite, the old range-combining logic can still
discard the refined bounds after the verifier learns them.

The new selftest uses bpf_skb_load_bytes() only to create a helper status in
r0 and run through the normal tc test-run path. It reproduces the verifier
state shape without requiring fexit attach or bpf_get_func_ret().

I would like this fix to be applied to the supported 6.6.y, 6.1.y,
5.15.y, and 5.10.y stable trees. This v2 targets 6.6.y first for stable
ordering. The same issue is also reproducible on 6.1.y, 5.15.y, and
5.10.y, but those trees need separate older-layout adaptations.

Targeted BPF selftest/reproducer results are:

  For 5.10.y and 5.15.y, I used the same minimized reproducer bytecode in
  QEMU because those trees still use the older test_verifier framework.

  v5.10.258:                         FAIL
  v5.10.258 + equivalent backport:   PASS
  v5.15.209:                         FAIL
  v5.15.209 + equivalent backport:   PASS
  v6.1.91:                         FAIL
  v6.1.91 + RFC backport series:   PASS
  v6.6.142:                        FAIL
  v6.6.142 + this series:          PASS
  v6.7.12:                         FAIL
  v6.8:                            PASS

I also checked bpf-next: bpf-next passes even when the d028f87517d6 JNE
refinement is reverted, because newer kernels also have the later
4bf79f9be434e ("bpf: Track equal scalars history on per-instruction level")
precision-tracking change. I did not use 4bf79f9be434e as the stable
backport base because it is a broader jmp_history/precision-tracking change
for linked scalars. For 6.6.y this series keeps the smaller stable backport
path that directly follows the bisected fix: preserve scalar bounds after
conditional refinement, then add the not-equal range refinement in the older
reg_set_min_max() layout.

Changes since RFC v1:
  - drop RFC;
  - state the intended stable targets and keep 6.6.y first for stable
    ordering;
  - add a BPF selftest covering the failure;
  - add 5.10.y and 5.15.y reproducer validation;
  - document why Rust-generated eBPF can naturally create this state shape;
  - note the later 4bf79f9be434e precision-tracking reason why bpf-next can
    pass independently.

RFC v1:
  https://lore.kernel.org/r/20260601180400.1381736-1-jt26wzz@gmail.com/

Thanks to Shung-Hsi Yu for reviewing the RFC, pointing out that 6.6.y
should be handled first for stable ordering, and noting that bpf-next is
also protected by the later 4bf79f9be434e ("bpf: Track equal scalars
history on per-instruction level") precision-tracking change.

Zhenzhong Wu (3):
  bpf: drop knowledge-losing __reg_combine_{32,64}_into_{64,32} logic
  bpf: make the verifier tracks the "not equal" for regs
  selftests/bpf: add helper retval linked scalar pruning test

 kernel/bpf/verifier.c                         | 92 ++++++++-----------
 .../selftests/bpf/progs/verifier_reg_equal.c  | 35 +++++++
 2 files changed, 75 insertions(+), 52 deletions(-)

base-commit: 924b4a879cbb75aef37c160b955b92f6894b11a4
-- 
2.43.0

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

end of thread, other threads:[~2026-06-08 10:11 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-07 17:09 [PATCH stable 6.6.y v2 0/3] bpf: backport scalar not-equal tracking fixes Zhenzhong Wu
2026-06-07 17:09 ` [PATCH stable 6.6.y v2 1/3] bpf: drop knowledge-losing __reg_combine_{32,64}_into_{64,32} logic Zhenzhong Wu
2026-06-07 17:09 ` [PATCH stable 6.6.y v2 2/3] bpf: make the verifier tracks the "not equal" for regs Zhenzhong Wu
2026-06-07 17:09 ` [PATCH stable 6.6.y v2 3/3] selftests/bpf: add helper retval linked scalar pruning test Zhenzhong Wu
2026-06-08 10:11 ` [PATCH stable 6.6.y v2 0/3] bpf: backport scalar not-equal tracking fixes Shung-Hsi Yu

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