* [PATCH bpf v2 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes
@ 2026-06-15 12:28 Yiyang Chen
2026-06-15 12:28 ` [PATCH bpf v2 1/2] " Yiyang Chen
` (2 more replies)
0 siblings, 3 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 12:28 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writer kfuncs can mutate skb packet data.
The verifier does not currently treat those kfuncs as packet-changing.
A direct packet pointer checked before the call can stay usable after the
write.
bpf_dynptr_write() already clears packet pointers through the helper path.
Teach kfunc argument checking to do the same for skb and skb-meta dynptr
destinations.
Keep source-only dynptr arguments unchanged.
Validation:
Without this series:
linux-stable-v7.0.12 accepts the three stale packet pointer cases;
linux-mainline-v7.1-rc7 accepts the three stale packet pointer cases;
the source-only bpf_dynptr_copy() control loads on both kernels.
With this series applied:
patched bpf-next rejects the three stale packet pointer cases with
"invalid mem access 'scalar'";
the source-only bpf_dynptr_copy() control still loads.
Build and style checks:
git diff --check: OK
checkpatch.pl --strict --no-tree: OK
make O=$BUILD kernel/bpf/verifier.o: OK
make O=$BUILD -j$(nproc) bzImage: OK
dynptr_fail.bpf.o build against patched vmlinux BTF: OK
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
Changes in v2:
- Resend as a properly threaded series. No code changes.
Yiyang Chen (2):
bpf: Fix packet pointer invalidation for skb dynptr writes
selftests/bpf: Add skb dynptr writer packet invalidation tests
include/linux/bpf_verifier.h | 1 +
kernel/bpf/verifier.c | 62 ++++++++++++-
.../testing/selftests/bpf/progs/dynptr_fail.c | 89 +++++++++++++++++++
3 files changed, 151 insertions(+), 1 deletion(-)
base-commit: e4287bf34f97a88c7d9322f5bde828724c073a6b
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v2 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 12:28 [PATCH bpf v2 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
@ 2026-06-15 12:28 ` Yiyang Chen
2026-06-15 12:28 ` [PATCH bpf v2 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2 siblings, 0 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 12:28 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writer kfuncs can mutate skb packet storage.
The verifier currently treats kfunc calls as packet-changing only for
bpf_xdp_pull_data().
That leaves direct packet pointers usable after skb dynptr writer kfuncs.
The helper path already clears packet pointers for bpf_dynptr_write().
Mark kfunc calls packet-changing when argument 0 is a writable skb or
skb-meta dynptr destination. This covers bpf_dynptr_copy(),
bpf_dynptr_memset(), and probe/copy-from-user dynptr writers.
Source-only dynptr arguments are left unchanged.
Fixes: daec295a70941 ("bpf/helpers: Introduce bpf_dynptr_copy kfunc")
Fixes: a498ee7576de ("bpf: Implement dynptr copy kfuncs")
Fixes: 5fc5d8fded57 ("bpf: Add bpf_dynptr_memset() kfunc")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
include/linux/bpf_verifier.h | 1 +
kernel/bpf/verifier.c | 62 +++++++++++++++++++++++++++++++++++-
2 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 39a851e690ec4..c7d0c20a4961b 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1448,6 +1448,7 @@ struct bpf_kfunc_call_arg_meta {
/* Out parameters */
u8 release_regno;
bool r0_rdonly;
+ bool pkt_dynptr_write;
u32 ret_btf_id;
u64 r0_size;
u32 subprogno;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf281c..5ea51bd284f84 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11016,6 +11016,16 @@ enum special_kfunc_type {
KF_bpf_xdp_pull_data,
KF_bpf_dynptr_slice,
KF_bpf_dynptr_slice_rdwr,
+ KF_bpf_dynptr_copy,
+ KF_bpf_dynptr_memset,
+ KF_bpf_probe_read_user_dynptr,
+ KF_bpf_probe_read_kernel_dynptr,
+ KF_bpf_probe_read_user_str_dynptr,
+ KF_bpf_probe_read_kernel_str_dynptr,
+ KF_bpf_copy_from_user_dynptr,
+ KF_bpf_copy_from_user_str_dynptr,
+ KF_bpf_copy_from_user_task_dynptr,
+ KF_bpf_copy_from_user_task_str_dynptr,
KF_bpf_dynptr_clone,
KF_bpf_percpu_obj_new_impl,
KF_bpf_percpu_obj_new,
@@ -11096,6 +11106,27 @@ BTF_ID_UNUSED
#endif
BTF_ID(func, bpf_dynptr_slice)
BTF_ID(func, bpf_dynptr_slice_rdwr)
+BTF_ID(func, bpf_dynptr_copy)
+BTF_ID(func, bpf_dynptr_memset)
+#ifdef CONFIG_BPF_EVENTS
+BTF_ID(func, bpf_probe_read_user_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_dynptr)
+BTF_ID(func, bpf_probe_read_user_str_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_dynptr)
+BTF_ID(func, bpf_copy_from_user_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_str_dynptr)
+#else
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+#endif
BTF_ID(func, bpf_dynptr_clone)
BTF_ID(func, bpf_percpu_obj_new_impl)
BTF_ID(func, bpf_percpu_obj_new)
@@ -11229,7 +11260,33 @@ static bool is_kfunc_bpf_preempt_enable(struct bpf_kfunc_call_arg_meta *meta)
bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta)
{
- return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
+ return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data] ||
+ meta->pkt_dynptr_write;
+}
+
+static bool dynptr_type_pkt_data(enum bpf_dynptr_type type)
+{
+ return type == BPF_DYNPTR_TYPE_SKB ||
+ type == BPF_DYNPTR_TYPE_SKB_META;
+}
+
+static bool is_kfunc_pkt_dynptr_writer(struct bpf_kfunc_call_arg_meta *meta, u32 arg)
+{
+ u32 func_id = meta->func_id;
+
+ if (arg != 0)
+ return false;
+
+ return func_id == special_kfunc_list[KF_bpf_dynptr_copy] ||
+ func_id == special_kfunc_list[KF_bpf_dynptr_memset] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_str_dynptr];
}
static enum kfunc_ptr_arg_type
@@ -12214,6 +12271,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
&meta->ref_obj, &meta->dynptr);
if (ret < 0)
return ret;
+ if (is_kfunc_pkt_dynptr_writer(meta, i) &&
+ dynptr_type_pkt_data(meta->dynptr.type))
+ meta->pkt_dynptr_write = true;
break;
}
case KF_ARG_PTR_TO_ITER:
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v2 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests
2026-06-15 12:28 [PATCH bpf v2 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 12:28 ` [PATCH bpf v2 1/2] " Yiyang Chen
@ 2026-06-15 12:28 ` Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2 siblings, 0 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 12:28 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
Add verifier tests for direct packet pointers kept live across skb dynptr
writer kfunc calls.
The writer cases must reject because stale packet pointers must not remain
usable after the kfunc can mutate skb packet storage.
Add a bpf_dynptr_copy() control where the skb dynptr is only the source.
That case must continue to load because it does not write through the
skb-backed dynptr.
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
.../testing/selftests/bpf/progs/dynptr_fail.c | 89 +++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index 344fb2aa0813d..bd51c9b93742a 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1274,6 +1274,95 @@ 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;
+
+/* 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;
+}
+
+/* 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
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v3 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 12:28 [PATCH bpf v2 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 12:28 ` [PATCH bpf v2 1/2] " Yiyang Chen
2026-06-15 12:28 ` [PATCH bpf v2 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
@ 2026-06-15 14:14 ` Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 1/2] " Yiyang Chen
` (2 more replies)
2 siblings, 3 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 14:14 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writer kfuncs can mutate skb packet data.
The verifier does not currently treat those kfuncs as packet-changing.
A direct packet pointer checked before the call can stay usable after the
write.
bpf_dynptr_write() already clears packet pointers through the helper path.
Teach kfunc argument checking to do the same for skb and skb-meta dynptr
destinations. For static CFG analysis, conservatively classify dynptr writer
kfuncs as packet-changing so global subprogram summaries are correct even
before register states exist.
Keep source-only dynptr arguments unchanged.
v3 also treats unspecialized global-subprogram dynptr arguments as possibly
packet-backed in the precise verifier path. This covers packet pointer
invalidation inside a global subprogram body where the argument may point to an
skb dynptr provided by the caller.
Validation:
Rebase:
fetched bpf-next origin/master on 2026-06-15;
series base is e4287bf34f97a ("selftests/bpf: Work around llvm stack
overflow in crypto progs").
Without this series:
linux-stable-v7.0.12 accepts the three original stale packet pointer cases;
linux-mainline-v7.1-rc7 accepts the three original stale packet pointer
cases;
the source-only bpf_dynptr_copy() control loads on both kernels.
With this series applied:
patched bpf-next rejects the five stale packet pointer cases with
"invalid mem access 'scalar'";
the source-only bpf_dynptr_copy() control still loads;
QEMU direct-runner reports PATCH008_SUMMARY failures=0 total=6.
Build and style checks:
git diff --check HEAD~2..HEAD: OK
checkpatch.pl --strict --no-tree: OK
make O=$BUILD kernel/bpf/verifier.o kernel/bpf/cfg.o: OK
make O=$BUILD -j$(nproc) bzImage: OK
dynptr_fail.bpf.o build against patched vmlinux BTF: OK
v2:
https://lore.kernel.org/bpf/cover.1781525896.git.chenyy23@mails.tsinghua.edu.cn/
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
Changes in v3:
- Rebased onto fetched bpf-next origin/master (e4287bf34f97a).
- Split static CFG packet-changing detection from precise checked-argument
invalidation.
- Treat unspecialized global subprogram dynptr arguments as possibly
packet-backed for writer invalidation.
- Add global subprogram regression tests for caller-side and local stale
packet pointer invalidation.
Changes in v2:
- Resend as a properly threaded series. No code changes.
Yiyang Chen (2):
bpf: Fix packet pointer invalidation for skb dynptr writes
selftests/bpf: Add skb dynptr writer packet invalidation tests
include/linux/bpf_verifier.h | 3 +
kernel/bpf/cfg.c | 2 +-
kernel/bpf/verifier.c | 79 +++++++++-
.../testing/selftests/bpf/progs/dynptr_fail.c | 140 ++++++++++++++++++
4 files changed, 222 insertions(+), 2 deletions(-)
base-commit: e4287bf34f97a88c7d9322f5bde828724c073a6b
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v3 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 14:14 ` [PATCH bpf v3 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
@ 2026-06-15 14:14 ` Yiyang Chen
2026-06-15 15:52 ` Alexei Starovoitov
2026-06-15 14:14 ` [PATCH bpf v3 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2 siblings, 1 reply; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 14:14 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writer kfuncs can mutate packet data, but the verifier
leaves checked direct packet pointers usable after kfunc calls.
The bpf_dynptr_write() helper already invalidates packet pointers
through clear_all_pkt_pointers(). Make skb dynptr writer kfuncs
follow the same rule.
Keep two verifier predicates for this. CFG analysis runs before register
states are available, so conservatively mark dynptr writer kfuncs as
packet-changing for subprogram summaries. The normal verifier path uses
the checked dynptr argument and invalidates only when the written dynptr
is, or may be, skb-backed.
Global subprogram dynptr arguments are prepared as unspecialized local
dynptr pointers, so treat CONST_PTR_TO_DYNPTR local dynptr writer
destinations as possibly packet-backed. This keeps packet pointer
invalidation sound both after global subprogram calls and inside global
subprogram bodies.
Fixes: daec295a7094 ("bpf/helpers: Introduce bpf_dynptr_copy kfunc")
Fixes: a498ee7576de ("bpf: Implement dynptr copy kfuncs")
Fixes: 5fc5d8fded57 ("bpf: Add bpf_dynptr_memset() kfunc")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
include/linux/bpf_verifier.h | 3 ++
kernel/bpf/cfg.c | 2 +-
kernel/bpf/verifier.c | 79 +++++++++++++++++++++++++++++++++++-
3 files changed, 82 insertions(+), 2 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 39a851e690ec4..603f22220122e 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1448,6 +1448,7 @@ struct bpf_kfunc_call_arg_meta {
/* Out parameters */
u8 release_regno;
bool r0_rdonly;
+ bool pkt_dynptr_write;
u32 ret_btf_id;
u64 r0_size;
u32 subprogno;
@@ -1502,6 +1503,8 @@ static inline bool bpf_is_kfunc_sleepable(struct bpf_kfunc_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_SLEEPABLE;
}
+
+bool bpf_kfunc_may_change_pkt_data(struct bpf_kfunc_call_arg_meta *meta);
bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta);
struct bpf_iarray *bpf_iarray_realloc(struct bpf_iarray *old, size_t n_elem);
int bpf_copy_insn_array_uniq(struct bpf_map *map, u32 start, u32 end, u32 *off);
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 26d37066465f3..7ed6bdd986bc8 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -516,7 +516,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
*/
if (ret == 0 && bpf_is_kfunc_sleepable(&meta))
mark_subprog_might_sleep(env, t);
- if (ret == 0 && bpf_is_kfunc_pkt_changing(&meta))
+ if (ret == 0 && bpf_kfunc_may_change_pkt_data(&meta))
mark_subprog_changes_pkt_data(env, t);
if (ret == 0 && bpf_is_throw_kfunc(insn))
mark_subprog_might_throw(env, t);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf281c..9c1e3cccbd6de 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11016,6 +11016,16 @@ enum special_kfunc_type {
KF_bpf_xdp_pull_data,
KF_bpf_dynptr_slice,
KF_bpf_dynptr_slice_rdwr,
+ KF_bpf_dynptr_copy,
+ KF_bpf_dynptr_memset,
+ KF_bpf_probe_read_user_dynptr,
+ KF_bpf_probe_read_kernel_dynptr,
+ KF_bpf_probe_read_user_str_dynptr,
+ KF_bpf_probe_read_kernel_str_dynptr,
+ KF_bpf_copy_from_user_dynptr,
+ KF_bpf_copy_from_user_str_dynptr,
+ KF_bpf_copy_from_user_task_dynptr,
+ KF_bpf_copy_from_user_task_str_dynptr,
KF_bpf_dynptr_clone,
KF_bpf_percpu_obj_new_impl,
KF_bpf_percpu_obj_new,
@@ -11096,6 +11106,27 @@ BTF_ID_UNUSED
#endif
BTF_ID(func, bpf_dynptr_slice)
BTF_ID(func, bpf_dynptr_slice_rdwr)
+BTF_ID(func, bpf_dynptr_copy)
+BTF_ID(func, bpf_dynptr_memset)
+#ifdef CONFIG_BPF_EVENTS
+BTF_ID(func, bpf_probe_read_user_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_dynptr)
+BTF_ID(func, bpf_probe_read_user_str_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_dynptr)
+BTF_ID(func, bpf_copy_from_user_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_str_dynptr)
+#else
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+#endif
BTF_ID(func, bpf_dynptr_clone)
BTF_ID(func, bpf_percpu_obj_new_impl)
BTF_ID(func, bpf_percpu_obj_new)
@@ -11227,9 +11258,52 @@ static bool is_kfunc_bpf_preempt_enable(struct bpf_kfunc_call_arg_meta *meta)
return meta->func_id == special_kfunc_list[KF_bpf_preempt_enable];
}
+static bool is_kfunc_pkt_dynptr_writer(struct bpf_kfunc_call_arg_meta *meta, u32 arg)
+{
+ u32 func_id = meta->func_id;
+
+ if (arg != 0)
+ return false;
+
+ return func_id == special_kfunc_list[KF_bpf_dynptr_copy] ||
+ func_id == special_kfunc_list[KF_bpf_dynptr_memset] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_str_dynptr];
+}
+
+bool bpf_kfunc_may_change_pkt_data(struct bpf_kfunc_call_arg_meta *meta)
+{
+ return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data] ||
+ is_kfunc_pkt_dynptr_writer(meta, 0);
+}
+
bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta)
{
- return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
+ return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data] ||
+ meta->pkt_dynptr_write;
+}
+
+static bool dynptr_type_pkt_data(enum bpf_dynptr_type type)
+{
+ return type == BPF_DYNPTR_TYPE_SKB ||
+ type == BPF_DYNPTR_TYPE_SKB_META;
+}
+
+static bool dynptr_may_be_pkt_data(const struct bpf_reg_state *reg,
+ enum bpf_dynptr_type type)
+{
+ if (dynptr_type_pkt_data(type))
+ return true;
+
+ /* Global subprog dynptr args are verified as unspecialized LOCAL. */
+ return reg->type == CONST_PTR_TO_DYNPTR &&
+ type == BPF_DYNPTR_TYPE_LOCAL;
}
static enum kfunc_ptr_arg_type
@@ -12214,6 +12288,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
&meta->ref_obj, &meta->dynptr);
if (ret < 0)
return ret;
+ if (is_kfunc_pkt_dynptr_writer(meta, i) &&
+ dynptr_may_be_pkt_data(reg, meta->dynptr.type))
+ meta->pkt_dynptr_write = true;
break;
}
case KF_ARG_PTR_TO_ITER:
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v3 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests
2026-06-15 14:14 ` [PATCH bpf v3 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 1/2] " Yiyang Chen
@ 2026-06-15 14:14 ` Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2 siblings, 0 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 14:14 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
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@mails.tsinghua.edu.cn>
---
.../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
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v4 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 14:14 ` [PATCH bpf v3 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 1/2] " Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
@ 2026-06-15 15:15 ` Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 1/2] " Yiyang Chen
` (2 more replies)
2 siblings, 3 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 15:15 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writers may mutate packet data and therefore must
invalidate checked direct packet pointers. The normal verifier path had
coverage for bpf_dynptr_write() with concrete skb dynptrs, but missed
unspecialized global subprogram dynptr arguments. skb dynptr writer
kfuncs also did not participate in packet pointer invalidation.
Fix both paths by adding conservative static CFG predicates for
bpf_dynptr_write() and skb dynptr writer kfuncs, while keeping the normal
verifier invalidation tied to the checked dynptr argument. Extend
dynptr_fail coverage for helper and kfunc writes, including global
subprogram caller-side and callee-side cases.
Validation, rebased and tested on bpf.git master 8cd9520d35a6
("Linux 7.1"):
git diff --check HEAD~2..HEAD: OK
scripts/checkpatch.pl --strict --no-tree: OK
make O=/tmp/patch008-v4-bpf-build olddefconfig: OK
make O=/tmp/patch008-v4-bpf-build -j$(nproc) \
kernel/bpf/verifier.o kernel/bpf/cfg.o: OK
clang --target=bpfel ... dynptr_fail.c: OK
make O=/tmp/patch008-v4-bpf-build -j$(nproc) bzImage: OK
QEMU replay on 7.1.0-g5a2ca20101d9: PATCH008_SUMMARY failures=0 total=8
Changes in v4:
- Rebase from bpf-next to current bpf.git master 8cd9520d35a6 to resolve
CI conflict.
- Address the helper-side global subprogram case for bpf_dynptr_write().
- Keep bpf_helper_changes_pkt_data() precise and add a separate static
bpf_helper_may_change_pkt_data() predicate for CFG summaries.
- Keep kfunc CFG summaries conservative while preserving checked-argument
precision in the normal verifier path.
- Extend selftests and QEMU replay coverage with bpf_dynptr_write()
global-subprogram cases.
- Link to v3: https://patch.msgid.link/cover.1781531784.git.chenyy23@mails.tsinghua.edu.cn
Yiyang Chen (2):
bpf: Fix packet pointer invalidation for skb dynptr writes
selftests/bpf: Add skb dynptr writer packet invalidation tests
include/linux/bpf_verifier.h | 3 +
include/linux/filter.h | 5 +
kernel/bpf/cfg.c | 4 +-
kernel/bpf/verifier.c | 88 ++++++++-
.../testing/selftests/bpf/progs/dynptr_fail.c | 183 ++++++++++++++++++
5 files changed, 278 insertions(+), 5 deletions(-)
base-commit: 8cd9520d35a6c38db6567e97dd93b1f11f185dc6
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v4 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 15:15 ` [PATCH bpf v4 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
@ 2026-06-15 15:15 ` Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2026-06-15 17:49 ` [PATCH bpf v5 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2 siblings, 0 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 15:15 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writers can mutate packet data, but not all verifier
paths invalidate checked direct packet pointers after those writes.
bpf_dynptr_write() handles skb and skb-meta dynptrs in the normal helper
path, but global subprogram dynptr arguments are verified as
unspecialized local dynptr pointers. Treat CONST_PTR_TO_DYNPTR local
arguments as possibly packet-backed for packet pointer invalidation.
Global subprogram summaries are computed during CFG analysis before
register states exist. Add conservative static CFG predicates for
bpf_dynptr_write() and skb dynptr writer kfuncs so caller-side packet
pointers are invalidated after global calls that may write packet data.
Keep the normal verifier invalidation precise: helpers and kfuncs still
use the checked dynptr argument and only invalidate when the written
dynptr is, or may be, skb-backed. Source-only dynptr arguments remain
unchanged.
Fixes: b5964b968ac6 ("bpf: Add skb dynptrs")
Fixes: daec295a7094 ("bpf/helpers: Introduce bpf_dynptr_copy kfunc")
Fixes: a498ee7576de ("bpf: Implement dynptr copy kfuncs")
Fixes: 5fc5d8fded57 ("bpf: Add bpf_dynptr_memset() kfunc")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
include/linux/bpf_verifier.h | 3 ++
include/linux/filter.h | 5 ++
kernel/bpf/cfg.c | 4 +-
kernel/bpf/verifier.c | 88 ++++++++++++++++++++++++++++++++++--
4 files changed, 95 insertions(+), 5 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 185b2aa43a420..b550e5a702081 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1332,6 +1332,7 @@ struct bpf_kfunc_call_arg_meta {
u32 ref_obj_id;
u8 release_regno;
bool r0_rdonly;
+ bool pkt_dynptr_write;
u32 ret_btf_id;
u64 r0_size;
u32 subprogno;
@@ -1389,6 +1390,8 @@ static inline bool bpf_is_kfunc_sleepable(struct bpf_kfunc_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_SLEEPABLE;
}
+
+bool bpf_kfunc_may_change_pkt_data(struct bpf_kfunc_call_arg_meta *meta);
bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta);
struct bpf_iarray *bpf_iarray_realloc(struct bpf_iarray *old, size_t n_elem);
int bpf_copy_insn_array_uniq(struct bpf_map *map, u32 start, u32 end, u32 *off);
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 88a241aac36a2..48485ca84d395 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1172,6 +1172,11 @@ void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp
u64 arch_bpf_timed_may_goto(void);
u64 bpf_check_timed_may_goto(struct bpf_timed_may_goto *);
bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id);
+static inline bool bpf_helper_may_change_pkt_data(enum bpf_func_id func_id)
+{
+ return bpf_helper_changes_pkt_data(func_id) ||
+ func_id == BPF_FUNC_dynptr_write;
+}
static inline bool bpf_dump_raw_ok(const struct cred *cred)
{
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 26d37066465f3..54a2130f1e465 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -483,7 +483,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
*/
if (ret == 0 && fp->might_sleep)
mark_subprog_might_sleep(env, t);
- if (bpf_helper_changes_pkt_data(insn->imm))
+ if (bpf_helper_may_change_pkt_data(insn->imm))
mark_subprog_changes_pkt_data(env, t);
if (insn->imm == BPF_FUNC_tail_call) {
ret = visit_abnormal_return_insn(env, t);
@@ -516,7 +516,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
*/
if (ret == 0 && bpf_is_kfunc_sleepable(&meta))
mark_subprog_might_sleep(env, t);
- if (ret == 0 && bpf_is_kfunc_pkt_changing(&meta))
+ if (ret == 0 && bpf_kfunc_may_change_pkt_data(&meta))
mark_subprog_changes_pkt_data(env, t);
if (ret == 0 && bpf_is_throw_kfunc(insn))
mark_subprog_might_throw(env, t);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7fb88e1cd7c4d..51468faa76a47 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9155,6 +9155,23 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
}));
}
+static bool dynptr_type_pkt_data(enum bpf_dynptr_type type)
+{
+ return type == BPF_DYNPTR_TYPE_SKB ||
+ type == BPF_DYNPTR_TYPE_SKB_META;
+}
+
+static bool dynptr_may_be_pkt_data(const struct bpf_reg_state *reg,
+ enum bpf_dynptr_type type)
+{
+ if (dynptr_type_pkt_data(type))
+ return true;
+
+ /* Global subprog dynptr args are verified as unspecialized LOCAL. */
+ return reg->type == CONST_PTR_TO_DYNPTR &&
+ type == BPF_DYNPTR_TYPE_LOCAL;
+}
+
enum {
AT_PKT_END = -1,
BEYOND_PKT_END = -2,
@@ -10520,8 +10537,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (dynptr_type == BPF_DYNPTR_TYPE_INVALID)
return -EFAULT;
- if (dynptr_type == BPF_DYNPTR_TYPE_SKB ||
- dynptr_type == BPF_DYNPTR_TYPE_SKB_META)
+ if (dynptr_may_be_pkt_data(reg, dynptr_type))
/* this will trigger clear_all_pkt_pointers(), which will
* invalidate all dynptr slices associated with the skb
*/
@@ -11157,6 +11173,16 @@ enum special_kfunc_type {
KF_bpf_xdp_pull_data,
KF_bpf_dynptr_slice,
KF_bpf_dynptr_slice_rdwr,
+ KF_bpf_dynptr_copy,
+ KF_bpf_dynptr_memset,
+ KF_bpf_probe_read_user_dynptr,
+ KF_bpf_probe_read_kernel_dynptr,
+ KF_bpf_probe_read_user_str_dynptr,
+ KF_bpf_probe_read_kernel_str_dynptr,
+ KF_bpf_copy_from_user_dynptr,
+ KF_bpf_copy_from_user_str_dynptr,
+ KF_bpf_copy_from_user_task_dynptr,
+ KF_bpf_copy_from_user_task_str_dynptr,
KF_bpf_dynptr_clone,
KF_bpf_percpu_obj_new_impl,
KF_bpf_percpu_obj_new,
@@ -11232,6 +11258,27 @@ BTF_ID_UNUSED
#endif
BTF_ID(func, bpf_dynptr_slice)
BTF_ID(func, bpf_dynptr_slice_rdwr)
+BTF_ID(func, bpf_dynptr_copy)
+BTF_ID(func, bpf_dynptr_memset)
+#ifdef CONFIG_BPF_EVENTS
+BTF_ID(func, bpf_probe_read_user_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_dynptr)
+BTF_ID(func, bpf_probe_read_user_str_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_dynptr)
+BTF_ID(func, bpf_copy_from_user_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_str_dynptr)
+#else
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+#endif
BTF_ID(func, bpf_dynptr_clone)
BTF_ID(func, bpf_percpu_obj_new_impl)
BTF_ID(func, bpf_percpu_obj_new)
@@ -11362,9 +11409,35 @@ static bool is_kfunc_bpf_preempt_enable(struct bpf_kfunc_call_arg_meta *meta)
return meta->func_id == special_kfunc_list[KF_bpf_preempt_enable];
}
+static bool is_kfunc_pkt_dynptr_writer(struct bpf_kfunc_call_arg_meta *meta, u32 arg)
+{
+ u32 func_id = meta->func_id;
+
+ if (arg != 0)
+ return false;
+
+ return func_id == special_kfunc_list[KF_bpf_dynptr_copy] ||
+ func_id == special_kfunc_list[KF_bpf_dynptr_memset] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_str_dynptr];
+}
+
+bool bpf_kfunc_may_change_pkt_data(struct bpf_kfunc_call_arg_meta *meta)
+{
+ return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data] ||
+ is_kfunc_pkt_dynptr_writer(meta, 0);
+}
+
bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta)
{
- return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
+ return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data] ||
+ meta->pkt_dynptr_write;
}
static enum kfunc_ptr_arg_type
@@ -12327,6 +12400,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
ret = process_dynptr_func(env, regno, insn_idx, dynptr_arg_type, clone_ref_obj_id);
if (ret < 0)
return ret;
+ if (is_kfunc_pkt_dynptr_writer(meta, i)) {
+ enum bpf_dynptr_type type;
+
+ type = dynptr_get_type(env, reg);
+ if (type == BPF_DYNPTR_TYPE_INVALID)
+ return -EFAULT;
+ if (dynptr_may_be_pkt_data(reg, type))
+ meta->pkt_dynptr_write = true;
+ }
if (!(dynptr_arg_type & MEM_UNINIT)) {
int id = dynptr_id(env, reg);
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v4 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests
2026-06-15 15:15 ` [PATCH bpf v4 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 1/2] " Yiyang Chen
@ 2026-06-15 15:15 ` Yiyang Chen
2026-06-15 16:26 ` bot+bpf-ci
2026-06-15 17:49 ` [PATCH bpf v5 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2 siblings, 1 reply; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 15:15 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
Add verifier tests for stale direct packet pointers after skb dynptr
writes through bpf_dynptr_write() and dynptr writer kfuncs.
Cover global subprogram cases for both caller-side packet pointer
invalidation and local packet pointer invalidation inside the subprogram
body. These cases exercise the static CFG summary path and the precise
verifier path for unspecialized global dynptr arguments.
Also cover direct kfunc writes through 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.
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
.../testing/selftests/bpf/progs/dynptr_fail.c | 183 ++++++++++++++++++
1 file changed, 183 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index b62773ce5219b..f8bd0483b68d0 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1232,6 +1232,189 @@ int skb_invalid_data_slice4(struct __sk_buff *skb)
return SK_PASS;
}
+char dynptr_writer_data[8] = "test";
+char dynptr_writer_dst[8];
+
+extern int bpf_dynptr_copy(struct bpf_dynptr *dst, __u64 dst_off,
+ struct bpf_dynptr *src, __u64 src_off,
+ __u64 size) __ksym __weak;
+extern int bpf_dynptr_memset(struct bpf_dynptr *ptr, __u64 offset,
+ __u64 size, __u8 val) __ksym __weak;
+extern int bpf_probe_read_kernel_dynptr(struct bpf_dynptr *dptr,
+ __u64 off, __u64 size,
+ const void *unsafe_ptr__ign) __ksym __weak;
+
+__noinline int global_dynptr_helper_write(struct bpf_dynptr *ptr)
+{
+ return bpf_dynptr_write(ptr, 0, dynptr_writer_data, 1, 0);
+}
+
+__noinline int global_dynptr_helper_write_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_write(ptr, 0, dynptr_writer_data, 1, 0);
+
+ /* this should fail */
+ return *data;
+}
+
+__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;
+}
+
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_after_global_dynptr_write(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_helper_write(&ptr);
+
+ /* this should fail */
+ return *data;
+}
+
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_inside_global_dynptr_write(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+
+ return global_dynptr_helper_write_and_read(skb, &ptr);
+}
+
+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;
+}
+
+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;
+}
+
+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);
+}
+
+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_writer_data, sizeof(dynptr_writer_data), 0, &src);
+ bpf_dynptr_copy(&dst, 0, &src, 0, 1);
+
+ /* this should fail */
+ return *data;
+}
+
+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_writer_dst, sizeof(dynptr_writer_dst), 0, &dst);
+ bpf_dynptr_copy(&dst, 0, &src, 0, 1);
+
+ return *data;
+}
+
+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_writer_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
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 14:14 ` [PATCH bpf v3 1/2] " Yiyang Chen
@ 2026-06-15 15:52 ` Alexei Starovoitov
2026-06-15 16:46 ` Yiyang Chen
0 siblings, 1 reply; 19+ messages in thread
From: Alexei Starovoitov @ 2026-06-15 15:52 UTC (permalink / raw)
To: Yiyang Chen, bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
John Fastabend, Shuah Khan, linux-kselftest, linux-kernel
On Mon Jun 15, 2026 at 7:14 AM PDT, Yiyang Chen wrote:
> skb-backed dynptr writer kfuncs can mutate packet data, but the verifier
> leaves checked direct packet pointers usable after kfunc calls.
> The bpf_dynptr_write() helper already invalidates packet pointers
> through clear_all_pkt_pointers(). Make skb dynptr writer kfuncs
> follow the same rule.
>
> Keep two verifier predicates for this. CFG analysis runs before register
> states are available, so conservatively mark dynptr writer kfuncs as
> packet-changing for subprogram summaries. The normal verifier path uses
> the checked dynptr argument and invalidates only when the written dynptr
> is, or may be, skb-backed.
>
> Global subprogram dynptr arguments are prepared as unspecialized local
> dynptr pointers, so treat CONST_PTR_TO_DYNPTR local dynptr writer
> destinations as possibly packet-backed. This keeps packet pointer
> invalidation sound both after global subprogram calls and inside global
> subprogram bodies.
>
> Fixes: daec295a7094 ("bpf/helpers: Introduce bpf_dynptr_copy kfunc")
> Fixes: a498ee7576de ("bpf: Implement dynptr copy kfuncs")
> Fixes: 5fc5d8fded57 ("bpf: Add bpf_dynptr_memset() kfunc")
> Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
nack.
pw-bot: cr
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH bpf v4 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests
2026-06-15 15:15 ` [PATCH bpf v4 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
@ 2026-06-15 16:26 ` bot+bpf-ci
0 siblings, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-06-15 16:26 UTC (permalink / raw)
To: chenyy23, bpf
Cc: chenyy23, ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, john.fastabend, shuah,
linux-kselftest, linux-kernel, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6040 bytes --]
> diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
> --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
> +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
> @@ -1232,6 +1232,189 @@ int skb_invalid_data_slice4(struct __sk_buff *skb)
> return SK_PASS;
> }
>
> +char dynptr_writer_data[8] = "test";
> +char dynptr_writer_dst[8];
> +
> +extern int bpf_dynptr_copy(struct bpf_dynptr *dst, __u64 dst_off,
> + struct bpf_dynptr *src, __u64 src_off,
> + __u64 size) __ksym __weak;
> +extern int bpf_dynptr_memset(struct bpf_dynptr *ptr, __u64 offset,
> + __u64 size, __u8 val) __ksym __weak;
> +extern int bpf_probe_read_kernel_dynptr(struct bpf_dynptr *dptr,
> + __u64 off, __u64 size,
> + const void *unsafe_ptr__ign) __ksym __weak;
> +
> +__noinline int global_dynptr_helper_write(struct bpf_dynptr *ptr)
> +{
> + return bpf_dynptr_write(ptr, 0, dynptr_writer_data, 1, 0);
> +}
> +
> +__noinline int global_dynptr_helper_write_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_write(ptr, 0, dynptr_writer_data, 1, 0);
> +
> + /* this should fail */
> + return *data;
> +}
> +
> +__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;
> +}
> +
> +SEC("?tc")
> +__failure __msg("invalid mem access 'scalar'")
> +int skb_pkt_ptr_invalid_after_global_dynptr_write(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_helper_write(&ptr);
> +
> + /* this should fail */
> + return *data;
> +}
> +
> +SEC("?tc")
> +__failure __msg("invalid mem access 'scalar'")
> +int skb_pkt_ptr_invalid_inside_global_dynptr_write(struct __sk_buff *skb)
> +{
> + struct bpf_dynptr ptr;
> +
> + bpf_dynptr_from_skb(skb, 0, &ptr);
> +
> + return global_dynptr_helper_write_and_read(skb, &ptr);
> +}
> +
> +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;
> +}
> +
> +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;
> +}
> +
> +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);
> +}
> +
> +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_writer_data, sizeof(dynptr_writer_data), 0, &src);
> + bpf_dynptr_copy(&dst, 0, &src, 0, 1);
> +
> + /* this should fail */
> + return *data;
> +}
> +
> +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_writer_dst, sizeof(dynptr_writer_dst), 0, &dst);
> + bpf_dynptr_copy(&dst, 0, &src, 0, 1);
> +
> + return *data;
> +}
> +
> +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_writer_data);
> +
> + /* this should fail */
> + return *data;
> +}
This patch series was NACKed by Alexei Starovoitov in v3 with the comment
"nack. pw-bot: cr" (change requested). The companion verifier fix patch
(patch 1/2 in the series) was rejected, which blocks this selftest patch
from landing. A v4 was posted to address the helper-side bpf_dynptr_write()
case, but has not yet received maintainer feedback.
Are there plans to address the maintainer feedback and resubmit the series?
Reference: https://lore.kernel.org/bpf/DJ9QS5OUJTSG.1DSRYHG6GAG3H@gmail.com/
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27557183694
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 15:52 ` Alexei Starovoitov
@ 2026-06-15 16:46 ` Yiyang Chen
2026-06-15 17:30 ` Alexei Starovoitov
0 siblings, 1 reply; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 16:46 UTC (permalink / raw)
To: alexei.starovoitov
Cc: andrii, ast, bpf, chenyy23, daniel, eddyz87, emil,
john.fastabend, jolsa, linux-kernel, linux-kselftest, martin.lau,
memxor, shuah, song, yonghong.song
Thanks, agreed that the previous version used bpf_is_kfunc_pkt_changing()
incorrectly. I will rework the fix.
Yiyang
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 16:46 ` Yiyang Chen
@ 2026-06-15 17:30 ` Alexei Starovoitov
2026-06-15 17:59 ` Yiyang Chen
0 siblings, 1 reply; 19+ messages in thread
From: Alexei Starovoitov @ 2026-06-15 17:30 UTC (permalink / raw)
To: Yiyang Chen
Cc: Andrii Nakryiko, Alexei Starovoitov, bpf, Daniel Borkmann,
Eduard, Emil Tsalapatis, John Fastabend, Jiri Olsa, LKML,
open list:KERNEL SELFTEST FRAMEWORK, Martin KaFai Lau,
Kumar Kartikeya Dwivedi, Shuah Khan, Song Liu, Yonghong Song
On Mon, Jun 15, 2026 at 9:46 AM Yiyang Chen
<chenyy23@mails.tsinghua.edu.cn> wrote:
>
> Thanks, agreed that the previous version used bpf_is_kfunc_pkt_changing()
> incorrectly. I will rework the fix.
Pls don't. There is nothing to fix.
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v5 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 15:15 ` [PATCH bpf v4 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 1/2] " Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
@ 2026-06-15 17:49 ` Yiyang Chen
2026-06-15 17:49 ` [PATCH bpf v5 1/2] " Yiyang Chen
2026-06-15 17:49 ` [PATCH bpf v5 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2 siblings, 2 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 17:49 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writers may mutate packet data and therefore must
invalidate checked direct packet pointers. The normal verifier path had
coverage for bpf_dynptr_write() with concrete skb dynptrs, but missed
unspecialized global subprogram dynptr arguments. skb dynptr writer
kfuncs also did not participate in packet pointer invalidation.
Fix both paths by adding conservative static CFG predicates for
bpf_dynptr_write() and skb dynptr writer kfuncs, while keeping the normal
verifier invalidation tied to the checked dynptr argument. Track when a
LOCAL dynptr may actually be packet-backed, and propagate that state
through dynptr clones and returned dynptr slices.
Validation, rebased and tested on bpf.git master 0e0611827f33
("Merge tag 'pull-fixes' of gitolite.kernel.org:pub/scm/linux/kernel/git/viro/vfs"):
git diff --check HEAD~2..HEAD: OK
scripts/checkpatch.pl --strict --no-tree: OK
make O=/tmp/patch008-v4-bpf-build -j16 \
kernel/bpf/verifier.o kernel/bpf/cfg.o kernel/bpf/states.o: OK
clang --target=bpfel ... dynptr_fail.c: OK
make O=/tmp/patch008-v4-bpf-build -j16 bzImage: OK
QEMU replay on 7.1.0-g7feeed42d8b9:
PATCH008_SUMMARY failures=0 total=10
Changes in v5:
- Carry maybe-packet-backed dynptr state for unspecialized global
subprogram dynptr arguments.
- Propagate that state through bpf_dynptr_clone() and dynptr slice returns.
- Invalidate LOCAL-typed dynptr slices when they may refer to packet data.
- Include the new dynptr state bit in stack state pruning comparisons.
- Add selftests for the global-dynptr clone and LOCAL-slice bypasses.
- Rebase onto bpf.git master 0e0611827f33.
- Link to v4: https://patch.msgid.link/cover.1781535194.git.chenyy23@mails.tsinghua.edu.cn
Yiyang Chen (2):
bpf: Fix packet pointer invalidation for skb dynptr writes
selftests/bpf: Add skb dynptr writer packet invalidation tests
include/linux/bpf_verifier.h | 6 +
include/linux/filter.h | 5 +
kernel/bpf/cfg.c | 4 +-
kernel/bpf/states.c | 2 +
kernel/bpf/verifier.c | 145 +++++++++--
.../testing/selftests/bpf/progs/dynptr_fail.c | 238 ++++++++++++++++++
6 files changed, 382 insertions(+), 18 deletions(-)
base-commit: 0e0611827f3349d0a2ac121c023a6d3260dcecdb
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v5 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 17:49 ` [PATCH bpf v5 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
@ 2026-06-15 17:49 ` Yiyang Chen
2026-06-15 17:52 ` Alexei Starovoitov
2026-06-15 17:49 ` [PATCH bpf v5 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
1 sibling, 1 reply; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 17:49 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
skb-backed dynptr writers can mutate packet data, but not all verifier
paths invalidate checked direct packet pointers after those writes.
bpf_dynptr_write() handles skb and skb-meta dynptrs in the normal helper
path, but global subprogram dynptr arguments are verified as
unspecialized local dynptr pointers. Treat such arguments as possibly
packet-backed for packet pointer invalidation.
Carry that possibly-packet-backed state through dynptr clones and dynptr
slices. Otherwise a global subprogram can clone its dynptr argument and
write through the stack clone, or obtain a LOCAL-typed dynptr slice and
write through the original dynptr, without invalidating packet pointers
or slice pointers that may refer to reallocated skb data.
Global subprogram summaries are computed during CFG analysis before
register states exist. Add conservative static CFG predicates for
bpf_dynptr_write() and skb dynptr writer kfuncs so caller-side packet
pointers are invalidated after global calls that may write packet data.
Keep the normal verifier invalidation precise: helpers and kfuncs still
use the checked dynptr argument and only invalidate when the written
dynptr is, or may be, skb-backed. Source-only dynptr arguments remain
unchanged.
Fixes: b5964b968ac6 ("bpf: Add skb dynptrs")
Fixes: daec295a7094 ("bpf/helpers: Introduce bpf_dynptr_copy kfunc")
Fixes: a498ee7576de ("bpf: Implement dynptr copy kfuncs")
Fixes: 5fc5d8fded57 ("bpf: Add bpf_dynptr_memset() kfunc")
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
include/linux/bpf_verifier.h | 6 ++
include/linux/filter.h | 5 ++
kernel/bpf/cfg.c | 4 +-
kernel/bpf/states.c | 2 +
kernel/bpf/verifier.c | 145 +++++++++++++++++++++++++++++++----
5 files changed, 144 insertions(+), 18 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 185b2aa43a420..4ce1f557492a1 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -66,11 +66,13 @@ struct bpf_reg_state {
struct { /* for PTR_TO_MEM | PTR_TO_MEM_OR_NULL */
u32 mem_size;
u32 dynptr_id; /* for dynptr slices */
+ bool dynptr_may_be_pkt_data; /* for dynptr slices */
};
/* For dynptr stack slots */
struct {
enum bpf_dynptr_type type;
+ bool may_be_pkt_data;
/* A dynptr is 16 bytes so it takes up 2 stack slots.
* We need to track which slot is the first slot
* to protect against cases where the user may try to
@@ -1332,6 +1334,7 @@ struct bpf_kfunc_call_arg_meta {
u32 ref_obj_id;
u8 release_regno;
bool r0_rdonly;
+ bool pkt_dynptr_write;
u32 ret_btf_id;
u64 r0_size;
u32 subprogno;
@@ -1365,6 +1368,7 @@ struct bpf_kfunc_call_arg_meta {
enum bpf_dynptr_type type;
u32 id;
u32 ref_obj_id;
+ bool may_be_pkt_data;
} initialized_dynptr;
struct {
u8 spi;
@@ -1389,6 +1393,8 @@ static inline bool bpf_is_kfunc_sleepable(struct bpf_kfunc_call_arg_meta *meta)
{
return meta->kfunc_flags & KF_SLEEPABLE;
}
+
+bool bpf_kfunc_may_change_pkt_data(struct bpf_kfunc_call_arg_meta *meta);
bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta);
struct bpf_iarray *bpf_iarray_realloc(struct bpf_iarray *old, size_t n_elem);
int bpf_copy_insn_array_uniq(struct bpf_map *map, u32 start, u32 end, u32 *off);
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 88a241aac36a2..48485ca84d395 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1172,6 +1172,11 @@ void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp
u64 arch_bpf_timed_may_goto(void);
u64 bpf_check_timed_may_goto(struct bpf_timed_may_goto *);
bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id);
+static inline bool bpf_helper_may_change_pkt_data(enum bpf_func_id func_id)
+{
+ return bpf_helper_changes_pkt_data(func_id) ||
+ func_id == BPF_FUNC_dynptr_write;
+}
static inline bool bpf_dump_raw_ok(const struct cred *cred)
{
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 26d37066465f3..54a2130f1e465 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -483,7 +483,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
*/
if (ret == 0 && fp->might_sleep)
mark_subprog_might_sleep(env, t);
- if (bpf_helper_changes_pkt_data(insn->imm))
+ if (bpf_helper_may_change_pkt_data(insn->imm))
mark_subprog_changes_pkt_data(env, t);
if (insn->imm == BPF_FUNC_tail_call) {
ret = visit_abnormal_return_insn(env, t);
@@ -516,7 +516,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
*/
if (ret == 0 && bpf_is_kfunc_sleepable(&meta))
mark_subprog_might_sleep(env, t);
- if (ret == 0 && bpf_is_kfunc_pkt_changing(&meta))
+ if (ret == 0 && bpf_kfunc_may_change_pkt_data(&meta))
mark_subprog_changes_pkt_data(env, t);
if (ret == 0 && bpf_is_throw_kfunc(insn))
mark_subprog_might_throw(env, t);
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 8478d2c6ed5b6..e9e8cd58bcb53 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -798,6 +798,8 @@ static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,
old_reg = &old->stack[spi].spilled_ptr;
cur_reg = &cur->stack[spi].spilled_ptr;
if (old_reg->dynptr.type != cur_reg->dynptr.type ||
+ old_reg->dynptr.may_be_pkt_data !=
+ cur_reg->dynptr.may_be_pkt_data ||
old_reg->dynptr.first_slot != cur_reg->dynptr.first_slot ||
!check_ids(old_reg->ref_obj_id, cur_reg->ref_obj_id, idmap))
return false;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7fb88e1cd7c4d..43a1e60cdf80b 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -527,8 +527,8 @@ static bool is_spi_bounds_valid(struct bpf_func_state *state, int spi, int nr_sl
return spi - nr_slots + 1 >= 0 && spi < allocated_slots;
}
-static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- const char *obj_kind, int nr_slots)
+static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ const char *obj_kind, int nr_slots)
{
int off, spi;
@@ -554,7 +554,7 @@ static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_s
return spi;
}
-static int dynptr_get_spi(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
+static int dynptr_get_spi(struct bpf_verifier_env *env, const struct bpf_reg_state *reg)
{
return stack_slot_obj_get_spi(env, reg, "dynptr", BPF_DYNPTR_NR_SLOTS);
}
@@ -622,12 +622,15 @@ static void __mark_dynptr_reg(struct bpf_reg_state *reg,
static void mark_dynptr_stack_regs(struct bpf_verifier_env *env,
struct bpf_reg_state *sreg1,
struct bpf_reg_state *sreg2,
- enum bpf_dynptr_type type)
+ enum bpf_dynptr_type type,
+ bool may_be_pkt_data)
{
int id = ++env->id_gen;
__mark_dynptr_reg(sreg1, type, true, id);
__mark_dynptr_reg(sreg2, type, false, id);
+ sreg1->dynptr.may_be_pkt_data = may_be_pkt_data;
+ sreg2->dynptr.may_be_pkt_data = may_be_pkt_data;
}
static void mark_dynptr_cb_reg(struct bpf_verifier_env *env,
@@ -641,7 +644,8 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
struct bpf_func_state *state, int spi);
static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- enum bpf_arg_type arg_type, int insn_idx, int clone_ref_obj_id)
+ enum bpf_arg_type arg_type, int insn_idx, int clone_ref_obj_id,
+ bool may_be_pkt_data)
{
struct bpf_func_state *state = bpf_func(env, reg);
enum bpf_dynptr_type type;
@@ -677,7 +681,8 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_
return -EINVAL;
mark_dynptr_stack_regs(env, &state->stack[spi].spilled_ptr,
- &state->stack[spi - 1].spilled_ptr, type);
+ &state->stack[spi - 1].spilled_ptr, type,
+ may_be_pkt_data);
if (dynptr_type_refcounted(type)) {
/* The id is used to track proper releasing */
@@ -1882,8 +1887,9 @@ static bool reg_is_pkt_pointer_any(const struct bpf_reg_state *reg)
static bool reg_is_dynptr_slice_pkt(const struct bpf_reg_state *reg)
{
return base_type(reg->type) == PTR_TO_MEM &&
- (reg->type &
- (DYNPTR_TYPE_SKB | DYNPTR_TYPE_XDP | DYNPTR_TYPE_SKB_META));
+ (reg->dynptr_may_be_pkt_data ||
+ (reg->type &
+ (DYNPTR_TYPE_SKB | DYNPTR_TYPE_XDP | DYNPTR_TYPE_SKB_META)));
}
/* Unmodified PTR_TO_PACKET[_META,_END] register from ctx access. */
@@ -7452,7 +7458,8 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
* type, and declare it as 'const struct bpf_dynptr *' in their prototype.
*/
static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn_idx,
- enum bpf_arg_type arg_type, int clone_ref_obj_id)
+ enum bpf_arg_type arg_type, int clone_ref_obj_id,
+ bool clone_may_be_pkt_data)
{
struct bpf_reg_state *reg = reg_state(env, regno);
int err;
@@ -7503,7 +7510,9 @@ static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn
return err;
}
- err = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx, clone_ref_obj_id);
+ err = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx,
+ clone_ref_obj_id,
+ clone_may_be_pkt_data);
} else /* MEM_RDONLY and None case from above */ {
/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
if (reg->type == CONST_PTR_TO_DYNPTR && !(arg_type & MEM_RDONLY)) {
@@ -8711,7 +8720,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
true, meta);
break;
case ARG_PTR_TO_DYNPTR:
- err = process_dynptr_func(env, regno, insn_idx, arg_type, 0);
+ err = process_dynptr_func(env, regno, insn_idx, arg_type, 0, false);
if (err)
return err;
break;
@@ -9155,6 +9164,35 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
}));
}
+static bool dynptr_type_pkt_data(enum bpf_dynptr_type type)
+{
+ return type == BPF_DYNPTR_TYPE_SKB ||
+ type == BPF_DYNPTR_TYPE_SKB_META;
+}
+
+static bool dynptr_may_be_pkt_data(struct bpf_verifier_env *env,
+ const struct bpf_reg_state *reg,
+ enum bpf_dynptr_type type)
+{
+ struct bpf_func_state *state;
+ int spi;
+
+ if (dynptr_type_pkt_data(type))
+ return true;
+ if (type != BPF_DYNPTR_TYPE_LOCAL)
+ return false;
+
+ if (reg->type == CONST_PTR_TO_DYNPTR)
+ return reg->dynptr.may_be_pkt_data;
+
+ state = bpf_func(env, reg);
+ spi = dynptr_get_spi(env, reg);
+ if (spi < 0)
+ return false;
+
+ return state->stack[spi].spilled_ptr.dynptr.may_be_pkt_data;
+}
+
enum {
AT_PKT_END = -1,
BEYOND_PKT_END = -2,
@@ -9370,7 +9408,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
if (ret)
return ret;
- ret = process_dynptr_func(env, regno, -1, arg->arg_type, 0);
+ ret = process_dynptr_func(env, regno, -1, arg->arg_type, 0, false);
if (ret)
return ret;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
@@ -10520,8 +10558,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (dynptr_type == BPF_DYNPTR_TYPE_INVALID)
return -EFAULT;
- if (dynptr_type == BPF_DYNPTR_TYPE_SKB ||
- dynptr_type == BPF_DYNPTR_TYPE_SKB_META)
+ if (dynptr_may_be_pkt_data(env, reg, dynptr_type))
/* this will trigger clear_all_pkt_pointers(), which will
* invalidate all dynptr slices associated with the skb
*/
@@ -11157,6 +11194,16 @@ enum special_kfunc_type {
KF_bpf_xdp_pull_data,
KF_bpf_dynptr_slice,
KF_bpf_dynptr_slice_rdwr,
+ KF_bpf_dynptr_copy,
+ KF_bpf_dynptr_memset,
+ KF_bpf_probe_read_user_dynptr,
+ KF_bpf_probe_read_kernel_dynptr,
+ KF_bpf_probe_read_user_str_dynptr,
+ KF_bpf_probe_read_kernel_str_dynptr,
+ KF_bpf_copy_from_user_dynptr,
+ KF_bpf_copy_from_user_str_dynptr,
+ KF_bpf_copy_from_user_task_dynptr,
+ KF_bpf_copy_from_user_task_str_dynptr,
KF_bpf_dynptr_clone,
KF_bpf_percpu_obj_new_impl,
KF_bpf_percpu_obj_new,
@@ -11232,6 +11279,27 @@ BTF_ID_UNUSED
#endif
BTF_ID(func, bpf_dynptr_slice)
BTF_ID(func, bpf_dynptr_slice_rdwr)
+BTF_ID(func, bpf_dynptr_copy)
+BTF_ID(func, bpf_dynptr_memset)
+#ifdef CONFIG_BPF_EVENTS
+BTF_ID(func, bpf_probe_read_user_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_dynptr)
+BTF_ID(func, bpf_probe_read_user_str_dynptr)
+BTF_ID(func, bpf_probe_read_kernel_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_dynptr)
+BTF_ID(func, bpf_copy_from_user_str_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_dynptr)
+BTF_ID(func, bpf_copy_from_user_task_str_dynptr)
+#else
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+BTF_ID_UNUSED
+#endif
BTF_ID(func, bpf_dynptr_clone)
BTF_ID(func, bpf_percpu_obj_new_impl)
BTF_ID(func, bpf_percpu_obj_new)
@@ -11362,9 +11430,35 @@ static bool is_kfunc_bpf_preempt_enable(struct bpf_kfunc_call_arg_meta *meta)
return meta->func_id == special_kfunc_list[KF_bpf_preempt_enable];
}
+static bool is_kfunc_pkt_dynptr_writer(struct bpf_kfunc_call_arg_meta *meta, u32 arg)
+{
+ u32 func_id = meta->func_id;
+
+ if (arg != 0)
+ return false;
+
+ return func_id == special_kfunc_list[KF_bpf_dynptr_copy] ||
+ func_id == special_kfunc_list[KF_bpf_dynptr_memset] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_probe_read_kernel_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_str_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_dynptr] ||
+ func_id == special_kfunc_list[KF_bpf_copy_from_user_task_str_dynptr];
+}
+
+bool bpf_kfunc_may_change_pkt_data(struct bpf_kfunc_call_arg_meta *meta)
+{
+ return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data] ||
+ is_kfunc_pkt_dynptr_writer(meta, 0);
+}
+
bool bpf_is_kfunc_pkt_changing(struct bpf_kfunc_call_arg_meta *meta)
{
- return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
+ return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data] ||
+ meta->pkt_dynptr_write;
}
static enum kfunc_ptr_arg_type
@@ -12288,6 +12382,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
case KF_ARG_PTR_TO_DYNPTR:
{
enum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR;
+ bool clone_may_be_pkt_data = false;
int clone_ref_obj_id = 0;
if (reg->type == CONST_PTR_TO_DYNPTR)
@@ -12318,15 +12413,27 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
clone_ref_obj_id = meta->initialized_dynptr.ref_obj_id;
+ clone_may_be_pkt_data = meta->initialized_dynptr.may_be_pkt_data;
if (dynptr_type_refcounted(parent_type) && !clone_ref_obj_id) {
verifier_bug(env, "missing ref obj id for parent of clone");
return -EFAULT;
}
}
- ret = process_dynptr_func(env, regno, insn_idx, dynptr_arg_type, clone_ref_obj_id);
+ ret = process_dynptr_func(env, regno, insn_idx, dynptr_arg_type,
+ clone_ref_obj_id,
+ clone_may_be_pkt_data);
if (ret < 0)
return ret;
+ if (is_kfunc_pkt_dynptr_writer(meta, i)) {
+ enum bpf_dynptr_type type;
+
+ type = dynptr_get_type(env, reg);
+ if (type == BPF_DYNPTR_TYPE_INVALID)
+ return -EFAULT;
+ if (dynptr_may_be_pkt_data(env, reg, type))
+ meta->pkt_dynptr_write = true;
+ }
if (!(dynptr_arg_type & MEM_UNINIT)) {
int id = dynptr_id(env, reg);
@@ -12338,6 +12445,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
meta->initialized_dynptr.id = id;
meta->initialized_dynptr.type = dynptr_get_type(env, reg);
meta->initialized_dynptr.ref_obj_id = dynptr_ref_obj_id(env, reg);
+ meta->initialized_dynptr.may_be_pkt_data =
+ dynptr_may_be_pkt_data(env, reg,
+ meta->initialized_dynptr.type);
}
break;
@@ -12956,6 +13066,8 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_ca
/* PTR_MAYBE_NULL will be added when is_kfunc_ret_null is checked */
regs[BPF_REG_0].type = PTR_TO_MEM | type_flag;
+ regs[BPF_REG_0].dynptr_may_be_pkt_data =
+ meta->initialized_dynptr.may_be_pkt_data;
if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice]) {
regs[BPF_REG_0].type |= MEM_RDONLY;
@@ -18763,6 +18875,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
} else if (arg->arg_type == (ARG_PTR_TO_DYNPTR | MEM_RDONLY)) {
/* assume unspecial LOCAL dynptr type */
__mark_dynptr_reg(reg, BPF_DYNPTR_TYPE_LOCAL, true, ++env->id_gen);
+ reg->dynptr.may_be_pkt_data = true;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
reg->type = PTR_TO_MEM;
reg->type |= arg->arg_type &
--
2.34.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH bpf v5 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests
2026-06-15 17:49 ` [PATCH bpf v5 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 17:49 ` [PATCH bpf v5 1/2] " Yiyang Chen
@ 2026-06-15 17:49 ` Yiyang Chen
2026-06-15 18:39 ` bot+bpf-ci
1 sibling, 1 reply; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 17:49 UTC (permalink / raw)
To: bpf
Cc: Yiyang Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, John Fastabend, Shuah Khan, linux-kselftest,
linux-kernel
Add verifier tests for stale direct packet pointers after skb dynptr
writes through bpf_dynptr_write() and dynptr writer kfuncs.
Cover global subprogram cases for both caller-side packet pointer
invalidation and local packet pointer invalidation inside the subprogram
body. These cases exercise the static CFG summary path and the precise
verifier path for unspecialized global dynptr arguments.
Also cover global subprogram bypasses where the dynptr argument is first
cloned to the stack, and where a LOCAL-typed dynptr slice is taken before
the original dynptr is written.
Also cover direct kfunc writes through 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.
Signed-off-by: Yiyang Chen <chenyy23@mails.tsinghua.edu.cn>
---
.../testing/selftests/bpf/progs/dynptr_fail.c | 238 ++++++++++++++++++
1 file changed, 238 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index b62773ce5219b..aa9374dbdb118 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -1232,6 +1232,244 @@ int skb_invalid_data_slice4(struct __sk_buff *skb)
return SK_PASS;
}
+char dynptr_writer_data[8] = "test";
+char dynptr_writer_dst[8];
+
+extern int bpf_dynptr_copy(struct bpf_dynptr *dst, __u64 dst_off,
+ struct bpf_dynptr *src, __u64 src_off,
+ __u64 size) __ksym __weak;
+extern int bpf_dynptr_memset(struct bpf_dynptr *ptr, __u64 offset,
+ __u64 size, __u8 val) __ksym __weak;
+extern int bpf_probe_read_kernel_dynptr(struct bpf_dynptr *dptr,
+ __u64 off, __u64 size,
+ const void *unsafe_ptr__ign) __ksym __weak;
+
+__noinline int global_dynptr_helper_write(struct bpf_dynptr *ptr)
+{
+ return bpf_dynptr_write(ptr, 0, dynptr_writer_data, 1, 0);
+}
+
+__noinline int global_dynptr_helper_write_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_write(ptr, 0, dynptr_writer_data, 1, 0);
+
+ /* this should fail */
+ return *data;
+}
+
+__noinline int global_dynptr_clone_write_and_read(struct __sk_buff *skb,
+ struct bpf_dynptr *ptr)
+{
+ __u8 *data = (void *)(long)skb->data;
+ __u8 *data_end = (void *)(long)skb->data_end;
+ struct bpf_dynptr clone;
+
+ if (data + 1 > data_end)
+ return SK_DROP;
+ if (bpf_dynptr_clone(ptr, &clone))
+ return SK_DROP;
+
+ bpf_dynptr_write(&clone, 0, dynptr_writer_data, 1, 0);
+
+ /* this should fail */
+ return *data;
+}
+
+__noinline int global_dynptr_slice_write_and_read(struct bpf_dynptr *ptr)
+{
+ __u8 buffer[1] = {};
+ __u8 *data;
+
+ data = bpf_dynptr_slice_rdwr(ptr, 0, buffer, sizeof(buffer));
+ if (!data)
+ return SK_DROP;
+
+ bpf_dynptr_write(ptr, 0, dynptr_writer_data, 1, 0);
+
+ /* this should fail */
+ return *data;
+}
+
+__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;
+}
+
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_after_global_dynptr_write(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_helper_write(&ptr);
+
+ /* this should fail */
+ return *data;
+}
+
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_inside_global_dynptr_write(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+
+ return global_dynptr_helper_write_and_read(skb, &ptr);
+}
+
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_pkt_ptr_invalid_after_global_dynptr_clone_write(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+
+ return global_dynptr_clone_write_and_read(skb, &ptr);
+}
+
+SEC("?tc")
+__failure __msg("invalid mem access 'scalar'")
+int skb_slice_invalid_after_global_dynptr_write(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+
+ return global_dynptr_slice_write_and_read(&ptr);
+}
+
+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;
+}
+
+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;
+}
+
+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);
+}
+
+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_writer_data, sizeof(dynptr_writer_data), 0, &src);
+ bpf_dynptr_copy(&dst, 0, &src, 0, 1);
+
+ /* this should fail */
+ return *data;
+}
+
+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_writer_dst, sizeof(dynptr_writer_dst), 0, &dst);
+ bpf_dynptr_copy(&dst, 0, &src, 0, 1);
+
+ return *data;
+}
+
+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_writer_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
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 17:49 ` [PATCH bpf v5 1/2] " Yiyang Chen
@ 2026-06-15 17:52 ` Alexei Starovoitov
0 siblings, 0 replies; 19+ messages in thread
From: Alexei Starovoitov @ 2026-06-15 17:52 UTC (permalink / raw)
To: Yiyang Chen
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
John Fastabend, Shuah Khan, open list:KERNEL SELFTEST FRAMEWORK,
LKML
On Mon, Jun 15, 2026 at 10:49 AM Yiyang Chen
<chenyy23@mails.tsinghua.edu.cn> wrote:
>
> skb-backed dynptr writers can mutate packet data, but not all verifier
> paths invalidate checked direct packet pointers after those writes.
>
> bpf_dynptr_write() handles skb and skb-meta dynptrs in the normal helper
> path, but global subprogram dynptr arguments are verified as
> unspecialized local dynptr pointers. Treat such arguments as possibly
> packet-backed for packet pointer invalidation.
>
> Carry that possibly-packet-backed state through dynptr clones and dynptr
> slices. Otherwise a global subprogram can clone its dynptr argument and
> write through the stack clone, or obtain a LOCAL-typed dynptr slice and
> write through the original dynptr, without invalidating packet pointers
> or slice pointers that may refer to reallocated skb data.
>
> Global subprogram summaries are computed during CFG analysis before
> register states exist. Add conservative static CFG predicates for
> bpf_dynptr_write() and skb dynptr writer kfuncs so caller-side packet
> pointers are invalidated after global calls that may write packet data.
>
> Keep the normal verifier invalidation precise: helpers and kfuncs still
> use the checked dynptr argument and only invalidate when the written
> dynptr is, or may be, skb-backed. Source-only dynptr arguments remain
> unchanged.
Nack.
Nothing to fix.
pw-bot: cr
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix packet pointer invalidation for skb dynptr writes
2026-06-15 17:30 ` Alexei Starovoitov
@ 2026-06-15 17:59 ` Yiyang Chen
0 siblings, 0 replies; 19+ messages in thread
From: Yiyang Chen @ 2026-06-15 17:59 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Yiyang Chen, Andrii Nakryiko, Alexei Starovoitov, bpf,
Daniel Borkmann, Eduard Zingerman, Emil Tsalapatis,
John Fastabend, Jiri Olsa, linux-kernel, linux-kselftest,
Martin KaFai Lau, Kumar Kartikeya Dwivedi, Shuah Khan, Song Liu,
Yonghong Song
I saw your reply after v5 had already been sent. Please disregard this
series.
Sorry for the noise.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH bpf v5 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests
2026-06-15 17:49 ` [PATCH bpf v5 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
@ 2026-06-15 18:39 ` bot+bpf-ci
0 siblings, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-06-15 18:39 UTC (permalink / raw)
To: chenyy23, bpf
Cc: chenyy23, ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, john.fastabend, shuah,
linux-kselftest, linux-kernel, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2000 bytes --]
> diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
> index b62773ce5219..aa9374dbdb11 100644
> --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
> +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
> @@ -1232,6 +1232,244 @@ int skb_invalid_data_slice4(struct __sk_buff *skb)
> return SK_PASS;
> }
>
[ ... ]
> +SEC("?tc")
> +__failure __msg("invalid mem access 'scalar'")
> +int skb_pkt_ptr_invalid_after_global_dynptr_write(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_helper_write(&ptr);
> +
> + /* this should fail */
> + return *data;
> +}
These new cases all assert __failure with __msg("invalid mem access
'scalar'"), meaning they expect the verifier to invalidate the direct
packet pointer after a skb dynptr write.
This expectation matches the verifier behavior change in the companion
patch 1/2 of the series. That verifier change was rejected during review:
Alexei Starovoitov replied "Nack. You misunderstand what
bpf_is_kfunc_pkt_changing() is doing." and later "There is nothing to
fix.", stating the existing verifier behavior is correct.
If the existing verifier behavior is correct and packet pointers are not
invalidated by these dynptr writes, would these __failure expectations
hold? It looks like they encode the disputed behavior from the rejected
patch 1/2, so should this selftest patch land on its own?
The author appears to have agreed in the discussion and withdrawn the
series. Was this patch intended to be dropped along with patch 1/2?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27565623381
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-06-15 18:39 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-15 12:28 [PATCH bpf v2 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 12:28 ` [PATCH bpf v2 1/2] " Yiyang Chen
2026-06-15 12:28 ` [PATCH bpf v2 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 1/2] " Yiyang Chen
2026-06-15 15:52 ` Alexei Starovoitov
2026-06-15 16:46 ` Yiyang Chen
2026-06-15 17:30 ` Alexei Starovoitov
2026-06-15 17:59 ` Yiyang Chen
2026-06-15 14:14 ` [PATCH bpf v3 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 1/2] " Yiyang Chen
2026-06-15 15:15 ` [PATCH bpf v4 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2026-06-15 16:26 ` bot+bpf-ci
2026-06-15 17:49 ` [PATCH bpf v5 0/2] bpf: Fix packet pointer invalidation for skb dynptr writes Yiyang Chen
2026-06-15 17:49 ` [PATCH bpf v5 1/2] " Yiyang Chen
2026-06-15 17:52 ` Alexei Starovoitov
2026-06-15 17:49 ` [PATCH bpf v5 2/2] selftests/bpf: Add skb dynptr writer packet invalidation tests Yiyang Chen
2026-06-15 18:39 ` bot+bpf-ci
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®