[PATCH bpf v3 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests

From: Yiyang Chen

Date: Mon Jun 15 2026 - 10:17:00 EST


Add verifier tests for skb dynptr writer kfuncs that must invalidate
checked direct packet pointers. Cover bpf_dynptr_memset(),
bpf_dynptr_copy() when the skb dynptr is the destination, and probe-read
into an skb dynptr.

Keep a source-only bpf_dynptr_copy() case as a positive control, since
copying from an skb dynptr into memory does not mutate packet data.

Add global subprogram regressions for stale caller packet pointers after
a dynptr writer call and stale packet pointer use inside a global
subprogram whose dynptr argument may be skb-backed.

Signed-off-by: Yiyang Chen <chenyy23@xxxxxxxxxxxxxxxxxxxxx>
---
.../testing/selftests/bpf/progs/dynptr_fail.c | 140 ++++++++++++++++++
1 file changed, 140 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index 344fb2aa0813d..d79cef5342d67 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1274,6 +1274,146 @@ int skb_invalid_data_slice4(struct __sk_buff *skb)
return SK_PASS;
}

+char dynptr_kfunc_data[8] = "test";
+char dynptr_kfunc_dst[8];
+
+extern int bpf_dynptr_copy(const struct bpf_dynptr *dst, __u64 dst_off,
+ const struct bpf_dynptr *src, __u64 src_off,
+ __u64 size) __ksym __weak;
+extern int bpf_dynptr_memset(const struct bpf_dynptr *ptr, __u64 offset,
+ __u64 size, __u8 val) __ksym __weak;
+extern int bpf_probe_read_kernel_dynptr(const struct bpf_dynptr *dptr,
+ __u64 off, __u64 size,
+ const void *unsafe_ptr__ign) __ksym __weak;
+
+__noinline int global_dynptr_kfunc_memset(struct bpf_dynptr *ptr)
+{
+ return bpf_dynptr_memset(ptr, 0, 1, 0);
+}
+
+__noinline int global_dynptr_kfunc_memset_and_read(struct __sk_buff *skb,
+ struct bpf_dynptr *ptr)
+{
+ __u8 *data = (void *)(long)skb->data;
+ __u8 *data_end = (void *)(long)skb->data_end;
+
+ if (data + 1 > data_end)
+ return SK_DROP;
+
+ bpf_dynptr_memset(ptr, 0, 1, 0);
+
+ /* this should fail */
+ return *data;
+}
+
+/* Direct packet pointers are invalidated after a dynptr kfunc writes to an skb */
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_after_dynptr_memset(struct __sk_buff *skb)
+{
+ __u8 *data = (void *)(long)skb->data;
+ __u8 *data_end = (void *)(long)skb->data_end;
+ struct bpf_dynptr ptr;
+
+ if (data + 1 > data_end)
+ return SK_DROP;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+ bpf_dynptr_memset(&ptr, 0, 1, 0);
+
+ /* this should fail */
+ return *data;
+}
+
+/* Global subprogs with dynptr writer kfuncs invalidate caller packet pointers */
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_after_global_dynptr_memset(struct __sk_buff *skb)
+{
+ __u8 *data = (void *)(long)skb->data;
+ __u8 *data_end = (void *)(long)skb->data_end;
+ struct bpf_dynptr ptr;
+
+ if (data + 1 > data_end)
+ return SK_DROP;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+ global_dynptr_kfunc_memset(&ptr);
+
+ /* this should fail */
+ return *data;
+}
+
+/* Global subprog dynptr args may be packet-backed and must invalidate locally */
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_inside_global_dynptr_memset(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+
+ return global_dynptr_kfunc_memset_and_read(skb, &ptr);
+}
+
+/* Direct packet pointers are invalidated after bpf_dynptr_copy() writes to an skb */
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_after_dynptr_copy_dst(struct __sk_buff *skb)
+{
+ __u8 *data = (void *)(long)skb->data;
+ __u8 *data_end = (void *)(long)skb->data_end;
+ struct bpf_dynptr dst, src;
+
+ if (data + 1 > data_end)
+ return SK_DROP;
+
+ bpf_dynptr_from_skb(skb, 0, &dst);
+ bpf_dynptr_from_mem(dynptr_kfunc_data, sizeof(dynptr_kfunc_data), 0, &src);
+ bpf_dynptr_copy(&dst, 0, &src, 0, 1);
+
+ /* this should fail */
+ return *data;
+}
+
+/* Direct packet pointers stay valid when an skb dynptr is only copied from */
+SEC("?tc")
+__success
+int skb_pkt_ptr_valid_after_dynptr_copy_src(struct __sk_buff *skb)
+{
+ __u8 *data = (void *)(long)skb->data;
+ __u8 *data_end = (void *)(long)skb->data_end;
+ struct bpf_dynptr dst, src;
+
+ if (data + 1 > data_end)
+ return SK_DROP;
+
+ bpf_dynptr_from_skb(skb, 0, &src);
+ bpf_dynptr_from_mem(dynptr_kfunc_dst, sizeof(dynptr_kfunc_dst), 0, &dst);
+ bpf_dynptr_copy(&dst, 0, &src, 0, 1);
+
+ return *data;
+}
+
+/* Direct packet pointers are invalidated after probe-read writes to an skb dynptr */
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_after_probe_read_kernel_dynptr(struct __sk_buff *skb)
+{
+ __u8 *data = (void *)(long)skb->data;
+ __u8 *data_end = (void *)(long)skb->data_end;
+ struct bpf_dynptr ptr;
+
+ if (data + 1 > data_end)
+ return SK_DROP;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+ bpf_probe_read_kernel_dynptr(&ptr, 0, 1, dynptr_kfunc_data);
+
+ /* this should fail */
+ return *data;
+}
+
/* Read-only skb data slice is invalidated on write to skb metadata */
SEC("?tc")
__failure __msg("invalid mem access 'scalar'")
--
2.34.1