Re: [PATCH bpf 1/3] bpf: Fix bpf_xdp_copy_buf() when the buffer overlaps the packet

From: Alexei Starovoitov

Date: Sat Oct 24 2026 - 20:57:50 EST


On Sat, Oct 24, 2026 at 02:26 AM Ayushman Rout <ayushmanrout27@xxxxxxxxx> wrote:
> + for (pass = 0; pass < 2; pass++) {

[...]

> + bool self = orig >= ptr_buf && orig < ptr_buf + ptr_len;
> + bool turn = pass == flush;
> +
> + if (self == turn) {

This is too clever for what it fixes.
Every load, store and bpf_xdp_output() that crosses into a frag now
walks the frags twice, to look for a buf that is on the stack, in a map
or in the perf buffer.

memmove() in bpf_xdp_load_bytes() and bpf_xdp_store_bytes() was a one
liner. This is not.
bpf_dynptr_copy() is there to move bytes within a packet, and it's the
only realistic way to get here with a slice: when one range fits into
a segment and the other one doesn't, it passes the slice of the first
as a buffer to __bpf_dynptr_read() or __bpf_dynptr_write().
With patch 2 the chunk loop copes with any overlap. Use it for every
copy within one packet and leave bpf_xdp_copy_buf() alone.
bpf_dynptr_read() and bpf_dynptr_write() with a slice as a buffer and
a range that crosses into a frag stay as they are. I don't think anyone
needs that.
The read and write tests of patch 3 become bpf_dynptr_copy() tests
where only one of the ranges crosses into frag0.
Something like this instead of patches 1 and 2 (compile tested only):

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 648abe1..b74c036 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -3291,8 +3291,10 @@ __bpf_kfunc int bpf_dynptr_copy(const struct bpf_dynptr *dst_ptr, u64 dst_off,
{
const struct bpf_dynptr_kern *dst = (struct bpf_dynptr_kern *)dst_ptr;
const struct bpf_dynptr_kern *src = (struct bpf_dynptr_kern *)src_ptr;
+ bool same = src->data == dst->data;
void *src_slice, *dst_slice;
char buf[256];
+ bool rev;
u64 off;

src_slice = bpf_dynptr_slice(src_ptr, src_off, NULL, size);
@@ -3303,25 +3305,31 @@ __bpf_kfunc int bpf_dynptr_copy(const struct bpf_dynptr *dst_ptr, u64 dst_off,
return 0;
}

- if (src_slice)
+ /* The other range is copied piece by piece. A slice of the same packet
+ * may overlap with it, so it cannot be the buffer for that.
+ */
+ if (src_slice && !same)
return __bpf_dynptr_write(dst, dst_off, src_slice, size, 0);

- if (dst_slice)
+ if (dst_slice && !same)
return __bpf_dynptr_read(dst_slice, size, src, src_off, 0);

if (bpf_dynptr_check_off_len(dst, dst_off, size) ||
bpf_dynptr_check_off_len(src, src_off, size))
return -E2BIG;

+ /* like memmove(): from the end if the destination is above the source */
+ rev = same && dst->offset + dst_off > src->offset + src_off;
off = 0;
while (off < size) {
u64 chunk_sz = min_t(u64, sizeof(buf), size - off);
+ u64 pos = rev ? size - off - chunk_sz : off;
int err;

- err = __bpf_dynptr_read(buf, chunk_sz, src, src_off + off, 0);
+ err = __bpf_dynptr_read(buf, chunk_sz, src, src_off + pos, 0);
if (err)
return err;
- err = __bpf_dynptr_write(dst, dst_off + off, buf, chunk_sz, 0);
+ err = __bpf_dynptr_write(dst, dst_off + pos, buf, chunk_sz, 0);
if (err)
return err;


pw-bot: cr