Commit 33a154a96e71 for kernel

commit 33a154a96e71a34a1bcca9f40da343dbbf7b38b4
Author: Alexei Starovoitov <ast@kernel.org>
Date:   Thu Oct 1 14:52:55 2026 +0000

    selftests/bpf: Test packet range of pointers sharing an id

    Add tests where two packet pointers share an id and tightening one
    pointer's umax from its var_off would put it less than their constant
    distance from the other's umax: with an index & 0x38 capped at 50, the
    base pointer keeps umax 50, so the pointer 8 bytes further on must keep
    umax 58, even though its known bits allow at most 56.

    These refused a valid program or accepted an out-of-bounds access before
    the fix:

    - check the advanced copy, load through the base: valid, was refused;
    - check the base, load the byte at base + 1 through a copy advanced by
      8: was accepted;
    - check base + 4, load 4 bytes at base + 2 through base + 8: reads two
      bytes past the checked range, was accepted;
    - the same as the second with data_meta pointers checked against data:
      was accepted.

    These pass with and without the fix and cover nearby paths:

    - subtract an unknown scalar from a checked pointer and load below it
      (the range is kept across a new id);
    - reach a load through two paths whose checks cover 8 and 7 bytes after
      the loaded pointer; the second path must not be pruned by the first;
    - spill a copy of a pointer, check the pointer, fill the copy and load
      one byte past the checked range: the load is refused, and the copy
      has the range of the check.

    Signed-off-by: Alexei Starovoitov <ast@kernel.org>
    Link: https://lore.kernel.org/bpf/20261001145255.855630-2-alexei.starovoitov@gmail.com
    Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>

diff --git a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
index 915a9707298b..139ff019d87d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
@@ -920,4 +920,182 @@ l1_%=:	r0 = *(u8*)(r9 + 0);				\
 	: __clobber_all);
 }

+SEC("tc")
+__description("direct packet access: 8-aligned offset, check p + 8, load 8 bytes at p")
+__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT)
+__naked void pkt_same_id_check_copy_load_base(void)
+{
+	asm volatile ("					\
+	r2 = *(u32*)(r1 + %[__sk_buff_data]);		\
+	r3 = *(u32*)(r1 + %[__sk_buff_data_end]);	\
+	r4 = *(u32*)(r1 + %[__sk_buff_mark]);		\
+	r4 &= 0x38;					\
+	if r4 > 50 goto l0_%=;				\
+	/* r4 is a multiple of 8, at most 48 */		\
+	r5 = r2;					\
+	r5 += r4;					\
+	r6 = r5;					\
+	r6 += 8;					\
+	if r6 > r3 goto l0_%=;				\
+	/* [r5, r5 + 8) is in the packet */		\
+	r0 = *(u64*)(r5 + 0);				\
+l0_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+	  __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+	  __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+	: __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: 8-aligned offset, check p, load past it via p + 8")
+__failure __msg("invalid access to packet, off=51 size=1")
+__naked void pkt_same_id_check_base_load_via_copy(void)
+{
+	asm volatile ("					\
+	r2 = *(u32*)(r1 + %[__sk_buff_data]);		\
+	r3 = *(u32*)(r1 + %[__sk_buff_data_end]);	\
+	r4 = *(u32*)(r1 + %[__sk_buff_mark]);		\
+	r4 &= 0x38;					\
+	if r4 > 50 goto l0_%=;				\
+	/* r4 is a multiple of 8, at most 48 */		\
+	r5 = r2;					\
+	r5 += r4;					\
+	r6 = r5;					\
+	r6 += 8;					\
+	if r5 > r3 goto l0_%=;				\
+	/* r6 - 7 is r5 + 1 */				\
+	r0 = *(u8*)(r6 - 7);				\
+l0_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+	  __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+	  __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+	: __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: 8-aligned offset, check p + 4, 4-byte load at p + 2")
+__failure __msg("invalid access to packet, off=52 size=4")
+__naked void pkt_same_id_check_base_4_load_via_copy(void)
+{
+	asm volatile ("					\
+	r2 = *(u32*)(r1 + %[__sk_buff_data]);		\
+	r3 = *(u32*)(r1 + %[__sk_buff_data_end]);	\
+	r4 = *(u32*)(r1 + %[__sk_buff_mark]);		\
+	r4 &= 0x38;					\
+	if r4 > 50 goto l0_%=;				\
+	/* r4 is a multiple of 8, at most 48 */		\
+	r5 = r2;					\
+	r5 += r4;					\
+	r6 = r5;					\
+	r6 += 8;					\
+	r7 = r5;					\
+	r7 += 4;					\
+	if r7 > r3 goto l0_%=;				\
+	/* [r5, r5 + 4) is in the packet, [r5 + 2, r5 + 6) may not be */ \
+	r0 = *(u32*)(r6 - 6);				\
+l0_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+	  __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+	  __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+	: __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: checked pointer minus non-negative unknown keeps range")
+__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT)
+__naked void pkt_sub_unknown_keeps_range(void)
+{
+	asm volatile ("					\
+	r2 = *(u32*)(r1 + %[__sk_buff_data]);		\
+	r3 = *(u32*)(r1 + %[__sk_buff_data_end]);	\
+	r4 = *(u32*)(r1 + %[__sk_buff_mark]);		\
+	r4 &= 0x1f;					\
+	r4 += 8;					\
+	r5 = r2;					\
+	r5 += 40;					\
+	if r5 > r3 goto l0_%=;				\
+	/* r5 is 8 to 39 bytes below the checked pointer */	\
+	r5 -= r4;					\
+	r0 = *(u64*)(r5 + 0);				\
+l0_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+	  __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+	  __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+	: __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: no pruning of a path whose checks prove fewer bytes")
+__failure __msg("invalid access to packet, off=255 size=8")
+__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ)
+__naked void pkt_same_id_pruning(void)
+{
+	asm volatile ("					\
+	r2 = *(u32*)(r1 + %[__sk_buff_data]);		\
+	r3 = *(u32*)(r1 + %[__sk_buff_data_end]);	\
+	r4 = *(u32*)(r1 + %[__sk_buff_mark]);		\
+	r0 = *(u32*)(r1 + %[__sk_buff_priority]);	\
+	r4 &= 0xff;					\
+	r5 = r2;					\
+	r5 += r4;					\
+	if r0 != 0 goto l1_%=;				\
+	/* this path proves [r5, r5 + 8) */		\
+	r6 = r5;					\
+	r6 += 8;					\
+	if r6 > r3 goto l0_%=;				\
+	goto l2_%=;					\
+l1_%=:	/* this path proves [r5, r5 + 7) */		\
+	if r5 > r3 goto l0_%=;				\
+	r6 = r5;					\
+	r6 += 7;					\
+	if r6 > r3 goto l0_%=;				\
+l2_%=:	r0 = *(u64*)(r5 + 0);				\
+l0_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+	  __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+	  __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)),
+	  __imm_const(__sk_buff_priority, offsetof(struct __sk_buff, priority))
+	: __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: spilled copy of checked pointer, load past range")
+__failure __msg("invalid access to packet, off=262 size=1, R7(id={{[0-9]+}},off=262,r=262)")
+__naked void pkt_spilled_copy_gets_range(void)
+{
+	asm volatile ("					\
+	r2 = *(u32*)(r1 + %[__sk_buff_data]);		\
+	r3 = *(u32*)(r1 + %[__sk_buff_data_end]);	\
+	r4 = *(u32*)(r1 + %[__sk_buff_mark]);		\
+	r4 &= 0xff;					\
+	r5 = r2;					\
+	r5 += r4;					\
+	*(u64*)(r10 - 8) = r5;				\
+	/* proves only bytes before r5 */		\
+	if r5 > r3 goto l0_%=;				\
+	r6 = r5;					\
+	r6 += 7;					\
+	if r6 > r3 goto l0_%=;				\
+	/* [r5, r5 + 7) is in the packet */		\
+	r7 = *(u64*)(r10 - 8);				\
+	r0 = *(u8*)(r7 + 7);				\
+l0_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+	  __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)),
+	  __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark))
+	: __clobber_all);
+}
+
 char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_meta_access.c b/tools/testing/selftests/bpf/progs/verifier_meta_access.c
index 62235f032ffe..c87e0be4b2ff 100644
--- a/tools/testing/selftests/bpf/progs/verifier_meta_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_meta_access.c
@@ -281,4 +281,34 @@ l0_%=:	r0 = 0;						\
 	: __clobber_all);
 }

+SEC("xdp")
+__description("meta access, 8-aligned offset, check p, load a byte at p + 1 via p + 8")
+__failure __msg("invalid access to packet, off=51 size=1")
+__naked void meta_access_check_base_load_via_copy(void)
+{
+	asm volatile ("					\
+	r9 = r1;					\
+	call %[bpf_get_prandom_u32];			\
+	r4 = r0;					\
+	r4 &= 0x38;					\
+	if r4 > 50 goto l0_%=;				\
+	/* r4 is a multiple of 8, at most 48 */		\
+	r2 = *(u32*)(r9 + %[xdp_md_data_meta]);		\
+	r3 = *(u32*)(r9 + %[xdp_md_data]);		\
+	r5 = r2;					\
+	r5 += r4;					\
+	r6 = r5;					\
+	r6 += 8;					\
+	if r5 > r3 goto l0_%=;				\
+	/* r6 - 7 is r5 + 1 */				\
+	r0 = *(u8*)(r6 - 7);				\
+l0_%=:	r0 = 0;						\
+	exit;						\
+"	:
+	: __imm(bpf_get_prandom_u32),
+	  __imm_const(xdp_md_data, offsetof(struct xdp_md, data)),
+	  __imm_const(xdp_md_data_meta, offsetof(struct xdp_md, data_meta))
+	: __clobber_all);
+}
+
 char _license[] SEC("license") = "GPL";