* [PATCH bpf 1/3] bpf: Fix bpf_xdp_copy_buf() when the buffer overlaps the packet
2026-10-24 9:26 [PATCH bpf 0/3] bpf: Fix overlapping copies in xdp and dynptr helpers Ayushman Rout
@ 2026-10-24 9:26 ` Ayushman Rout
2026-10-25 0:57 ` Alexei Starovoitov
2026-10-24 9:26 ` [PATCH bpf 3/3] selftests/bpf: Test overlapping dynptr copies on xdp frags Ayushman Rout
1 sibling, 1 reply; 4+ messages in thread
From: Ayushman Rout @ 2026-10-24 9:26 UTC (permalink / raw)
To: bpf
Cc: ast, Ayushman Rout, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, John Fastabend, Stanislav Fomichev,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jesper Dangaard Brouer, Daniel Rosenberg, netdev,
linux-kernel
bpf_xdp_copy_buf() moves data between a caller buffer and an xdp_buff
one segment at a time (the linear part, then each frag) with memcpy().
bpf_xdp_load_bytes(), bpf_xdp_store_bytes() and bpf_dynptr_slice() with
a buffer all use it.
The buffer does not have to be outside the packet. A program can pass a
slice of the same packet, for example one from bpf_dynptr_slice_rdwr().
Then a piece that is copied early can overwrite bytes that a later piece
still has to read. memcpy() is also not valid for overlapping memory.
Example: a 100 byte linear part, a 4096 byte frag0, a buffer at
frag0 + 10 and a 64 byte load from offset 90. The first piece copies
linear[90..100) to frag0[10..20), which destroys data that the second
piece needs from frag0[0..54). A store fails in the same way.
Commit ab08863c58b1 ("bpf: Use memmove() to store bytes in an
xdp_buff") fixed the case where the range is inside one segment. A
range that crosses from the linear part into a frag still goes through
here and is not covered.
Use memmove() for every piece. For a load, copy the piece that is in the
same segment as the buffer first. For a store, copy it last. This
assumes that the frags do not overlap each other.
The selftests added later in this series fail without this patch.
Fixes: 2012c867c800 ("bpf: verifier: Accept dynptr mem as mem in helpers")
Signed-off-by: Ayushman Rout <ayushmanrout27@gmail.com>
Assisted-by: LLM
---
net/core/filter.c | 59 ++++++++++++++++++++++++++++-------------------
1 file changed, 35 insertions(+), 24 deletions(-)
diff --git a/net/core/filter.c b/net/core/filter.c
index 09c60a58c82a..716efafe7e0a 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -4210,47 +4210,58 @@ static const struct bpf_func_proto bpf_xdp_adjust_head_proto = {
void bpf_xdp_copy_buf(struct xdp_buff *xdp, unsigned long off,
void *buf, unsigned long len, bool flush)
{
- unsigned long ptr_len, ptr_off = 0;
+ unsigned long ptr_len, ptr_off, o, l;
skb_frag_t *next_frag, *end_frag;
struct skb_shared_info *sinfo;
+ u8 *ptr_buf, *b, *orig = buf;
void *src, *dst;
- u8 *ptr_buf;
+ int pass;
if (likely(xdp->data_end - xdp->data >= off + len)) {
src = flush ? buf : xdp->data + off;
dst = flush ? xdp->data + off : buf;
- memcpy(dst, src, len);
+ memmove(dst, src, len);
return;
}
sinfo = xdp_get_shared_info_from_buff(xdp);
end_frag = &sinfo->frags[sinfo->nr_frags];
- next_frag = &sinfo->frags[0];
- ptr_len = xdp->data_end - xdp->data;
- ptr_buf = xdp->data;
+ for (pass = 0; pass < 2; pass++) {
+ o = off;
+ l = len;
+ b = buf;
+ ptr_off = 0;
+ ptr_len = xdp->data_end - xdp->data;
+ ptr_buf = xdp->data;
+ next_frag = &sinfo->frags[0];
+
+ while (true) {
+ if (o < ptr_off + ptr_len) {
+ unsigned long copy_off = o - ptr_off;
+ unsigned long copy_len = min(l, ptr_len - copy_off);
+ bool self = orig >= ptr_buf && orig < ptr_buf + ptr_len;
+ bool turn = pass == flush;
+
+ if (self == turn) {
+ src = flush ? b : ptr_buf + copy_off;
+ dst = flush ? ptr_buf + copy_off : b;
+ memmove(dst, src, copy_len);
+ }
- while (true) {
- if (off < ptr_off + ptr_len) {
- unsigned long copy_off = off - ptr_off;
- unsigned long copy_len = min(len, ptr_len - copy_off);
+ o += copy_len;
+ l -= copy_len;
+ b += copy_len;
+ }
- src = flush ? buf : ptr_buf + copy_off;
- dst = flush ? ptr_buf + copy_off : buf;
- memcpy(dst, src, copy_len);
+ if (!l || next_frag == end_frag)
+ break;
- off += copy_len;
- len -= copy_len;
- buf += copy_len;
+ ptr_off += ptr_len;
+ ptr_buf = skb_frag_address(next_frag);
+ ptr_len = skb_frag_size(next_frag);
+ next_frag++;
}
-
- if (!len || next_frag == end_frag)
- break;
-
- ptr_off += ptr_len;
- ptr_buf = skb_frag_address(next_frag);
- ptr_len = skb_frag_size(next_frag);
- next_frag++;
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH bpf 3/3] selftests/bpf: Test overlapping dynptr copies on xdp frags
2026-10-24 9:26 [PATCH bpf 0/3] bpf: Fix overlapping copies in xdp and dynptr helpers Ayushman Rout
2026-10-24 9:26 ` [PATCH bpf 1/3] bpf: Fix bpf_xdp_copy_buf() when the buffer overlaps the packet Ayushman Rout
@ 2026-10-24 9:26 ` Ayushman Rout
1 sibling, 0 replies; 4+ messages in thread
From: Ayushman Rout @ 2026-10-24 9:26 UTC (permalink / raw)
To: bpf
Cc: ast, Ayushman Rout, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
linux-kselftest, linux-kernel, netdev
Add four dynptr tests. Three use an xdp packet with frags, and in each
the range goes from the linear part into frag0:
- test_dynptr_copy_xdp_overlap: bpf_dynptr_copy() of 1200 bytes with
the destination 100 bytes above the source.
- test_dynptr_read_xdp_overlap: bpf_dynptr_read() into a slice of the
same packet.
- test_dynptr_write_xdp_overlap: bpf_dynptr_write() from a slice of the
same packet.
The fourth, test_dynptr_skb_load_overlap, reads a range that spans the
head and the frags of an skb. It passes with and without the fixes and
checks that skbs are not affected.
The new tests set err when the data is wrong. The existing
test_dynptr_copy_xdp does not, so it can pass on corrupted data.
Without the fixes to bpf_xdp_copy_buf() and bpf_dynptr_copy(), the three
xdp tests fail. With them, all four tests pass.
Signed-off-by: Ayushman Rout <ayushmanrout27@gmail.com>
Assisted-by: LLM
---
.../testing/selftests/bpf/prog_tests/dynptr.c | 4 +
.../selftests/bpf/progs/dynptr_success.c | 128 ++++++++++++++++++
2 files changed, 132 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c
index 4396560365e8..08382ed076a2 100644
--- a/tools/testing/selftests/bpf/prog_tests/dynptr.c
+++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c
@@ -22,6 +22,10 @@ static struct {
{"test_dynptr_data", SETUP_SYSCALL_SLEEP},
{"test_dynptr_copy", SETUP_SYSCALL_SLEEP},
{"test_dynptr_copy_xdp", SETUP_XDP_PROG},
+ {"test_dynptr_copy_xdp_overlap", SETUP_XDP_PROG},
+ {"test_dynptr_read_xdp_overlap", SETUP_XDP_PROG},
+ {"test_dynptr_write_xdp_overlap", SETUP_XDP_PROG},
+ {"test_dynptr_skb_load_overlap", SETUP_SKB_PROG_NONLINEAR},
{"test_dynptr_memset_zero", SETUP_SYSCALL_SLEEP},
{"test_dynptr_memset_notzero", SETUP_SYSCALL_SLEEP},
{"test_dynptr_memset_zero_offset", SETUP_SYSCALL_SLEEP},
diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c
index b668ebd61fc7..b6c3abb66e4b 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_success.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_success.c
@@ -759,6 +759,134 @@ int test_dynptr_copy_xdp(struct xdp_md *xdp)
return XDP_DROP;
}
+static __always_inline int fill_xdp(struct bpf_dynptr *ptr, __u32 start, __u32 n)
+{
+ __u8 b;
+ int i;
+
+ bpf_for(i, 0, n) {
+ b = (start + i) % 251;
+ if (bpf_dynptr_write(ptr, start + i, &b, 1, 0))
+ return 1;
+ }
+ return 0;
+}
+
+static __always_inline int check_xdp(struct bpf_dynptr *ptr, __u32 at, __u32 n, __u32 from)
+{
+ __u8 b;
+ int i;
+
+ bpf_for(i, 0, n) {
+ if (bpf_dynptr_read(&b, 1, ptr, at + i, 0))
+ return 1;
+ if (b != (__u8)((from + i) % 251))
+ return 1;
+ }
+ return 0;
+}
+
+SEC("?xdp")
+int test_dynptr_copy_xdp_overlap(struct xdp_md *xdp)
+{
+ void *data_end = (void *)(long)xdp->data_end;
+ void *data = (void *)(long)xdp->data;
+ __u32 off = data_end - data - 1000;
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_xdp(xdp, 0, &ptr);
+ if (fill_xdp(&ptr, off, 1300)) {
+ err = 1;
+ return XDP_DROP;
+ }
+ err = bpf_dynptr_copy(&ptr, off + 100, &ptr, off, 1200);
+ if (err)
+ return XDP_DROP;
+ if (check_xdp(&ptr, off + 100, 1200, off))
+ err = 2;
+ return XDP_DROP;
+}
+
+SEC("?xdp")
+int test_dynptr_read_xdp_overlap(struct xdp_md *xdp)
+{
+ void *data_end = (void *)(long)xdp->data_end;
+ void *data = (void *)(long)xdp->data;
+ __u32 lin = data_end - data;
+ struct bpf_dynptr ptr;
+ char *dst;
+
+ bpf_dynptr_from_xdp(xdp, 0, &ptr);
+ if (fill_xdp(&ptr, lin - 10, 84)) {
+ err = 1;
+ return XDP_DROP;
+ }
+ dst = bpf_dynptr_slice_rdwr(&ptr, lin + 10, NULL, 64);
+ if (!dst) {
+ err = 2;
+ return XDP_DROP;
+ }
+ err = bpf_dynptr_read(dst, 64, &ptr, lin - 10, 0);
+ if (err)
+ return XDP_DROP;
+ if (check_xdp(&ptr, lin + 10, 64, lin - 10))
+ err = 3;
+ return XDP_DROP;
+}
+
+SEC("?xdp")
+int test_dynptr_write_xdp_overlap(struct xdp_md *xdp)
+{
+ void *data_end = (void *)(long)xdp->data_end;
+ void *data = (void *)(long)xdp->data;
+ __u32 lin = data_end - data;
+ struct bpf_dynptr ptr;
+ char *src;
+
+ bpf_dynptr_from_xdp(xdp, 0, &ptr);
+ if (fill_xdp(&ptr, lin - 64, 118)) {
+ err = 1;
+ return XDP_DROP;
+ }
+ src = bpf_dynptr_slice_rdwr(&ptr, lin - 64, NULL, 64);
+ if (!src) {
+ err = 2;
+ return XDP_DROP;
+ }
+ err = bpf_dynptr_write(&ptr, lin - 10, src, 64, 0);
+ if (err)
+ return XDP_DROP;
+ if (check_xdp(&ptr, lin - 10, 64, lin - 64))
+ err = 3;
+ return XDP_DROP;
+}
+
+SEC("?tc")
+int test_dynptr_skb_load_overlap(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+ char ref[24];
+ char *dst;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+ if (bpf_dynptr_read(ref, 24, &ptr, 20, 0)) {
+ err = 1;
+ return 0;
+ }
+ dst = bpf_dynptr_slice_rdwr(&ptr, 10, NULL, 24);
+ if (!dst) {
+ err = 2;
+ return 0;
+ }
+ if (bpf_dynptr_read(dst, 24, &ptr, 20, 0)) {
+ err = 3;
+ return 0;
+ }
+ if (bpf_memcmp(dst, ref, 24))
+ err = 4;
+ return 0;
+}
+
char memset_zero_data[] = "data to be zeroed";
SEC("?tp/syscalls/sys_enter_nanosleep")
--
2.54.0
^ permalink raw reply related [flat|nested] 4+ messages in thread